diff --git a/config/config/src/main/java/io/helidon/config/BuilderImpl.java b/config/config/src/main/java/io/helidon/config/BuilderImpl.java index b30ce8f77..2153c58b2 100644 --- a/config/config/src/main/java/io/helidon/config/BuilderImpl.java +++ b/config/config/src/main/java/io/helidon/config/BuilderImpl.java @@ -18,7 +18,6 @@ package io.helidon.config; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -455,12 +454,7 @@ class BuilderImpl implements Config.Builder { hasEnvVarSource = true; hasSystemPropertiesSource = true; - prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.systemProperties() - .pollingStrategy(PollingStrategies - .regular(Duration.ofSeconds(2)) - .build()) - .build(), - 100)); + prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.systemProperties().build(), 100)); prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.environmentVariables(), 100)); prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.classpath("application.yaml") .optional(true) diff --git a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/HelidonMethodHandle.java b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/HelidonMethodHandle.java new file mode 100644 index 000000000..9fdfec953 --- /dev/null +++ b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/HelidonMethodHandle.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.config.objectmapping; + +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; + +/** + * A replacement for {@link java.lang.invoke.MethodHandle} that we cannot use for the time being, due to + * limitations of GraalVM native image. + */ +interface HelidonMethodHandle { + static HelidonMethodHandle create(Class type, Constructor constructor) { + return new ReflectionUtil.ConstructorMethodHandle(type, constructor); + } + + static HelidonMethodHandle create(Class type, Method method) { + if (Modifier.isStatic(method.getModifiers())) { + return new ReflectionUtil.StaticMethodHandle(method); + } else { + return new ReflectionUtil.InstanceMethodHandle(type, method); + } + } + + static HelidonMethodHandle create(Class type, Field field) { + return new ReflectionUtil.FieldMethodHandle(type, field); + } + + /** + * Invoke the method or constructor with params. + * @param params parameters + * @return response + */ + Object invoke(List params); + + /** + * Type of this handle, see {@link java.lang.invoke.MethodHandle#type()}. + * + * @return type of this handle + */ + MethodType type(); + + /** + * Invoke with varargs, delegates to {@link #invoke(java.util.List)}. + * + * @param params parameters + * @return result of the operation, or null (for setters) + */ + default Object invoke(Object... params) { + return invoke(Arrays.asList(params)); + } +} diff --git a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java index b6132f8a7..9b171ea8b 100644 --- a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java +++ b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,13 @@ */ package io.helidon.config.objectmapping; -import java.lang.invoke.MethodHandle; import java.util.Map; import java.util.Optional; import java.util.function.Function; import javax.annotation.Priority; +import io.helidon.common.HelidonFeatures; import io.helidon.config.Config; import io.helidon.config.objectmapping.ObjectConfigMappers.BuilderConfigMapper; import io.helidon.config.objectmapping.ObjectConfigMappers.ConfigMethodHandleConfigMapper; @@ -50,6 +50,10 @@ public class ObjectConfigMapperProvider implements ConfigMapperProvider { private static final String METHOD_PARSE = "parse"; private static final String METHOD_CREATE = "create"; + static { + HelidonFeatures.register("Config", "Object Mapping"); + } + @Override public Map, Function> mappers() { return Map.of(); @@ -110,7 +114,7 @@ public class ObjectConfigMapperProvider implements ConfigMapperProvider { private static Optional> findStaticStringMethodMapper(Class type, String methodName) { - Optional method = findStaticMethod(type, + Optional method = findStaticMethod(type, methodName, String.class); diff --git a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMappers.java b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMappers.java index 2559c936c..52fb8b685 100644 --- a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMappers.java +++ b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMappers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,10 @@ */ package io.helidon.config.objectmapping; -import java.lang.invoke.MethodHandle; import java.util.Collection; +import java.util.List; import java.util.function.Function; -import io.helidon.common.HelidonFeatures; import io.helidon.config.Config; import io.helidon.config.ConfigMappingException; import io.helidon.config.MissingValueException; @@ -31,16 +30,12 @@ import io.helidon.config.objectmapping.ReflectionUtil.PropertyAccessor; */ class ObjectConfigMappers { - static { - HelidonFeatures.register("Config", "Object Mapping"); - } - abstract static class MethodHandleConfigMapper implements Function { private final Class type; private final String methodName; - private final MethodHandle methodHandle; + private final HelidonMethodHandle methodHandle; - MethodHandleConfigMapper(Class type, String methodName, MethodHandle methodHandle) { + MethodHandleConfigMapper(Class type, String methodName, HelidonMethodHandle methodHandle) { this.type = type; this.methodName = methodName; this.methodHandle = methodHandle; @@ -51,7 +46,7 @@ class ObjectConfigMappers { @Override public T apply(Config config) throws ConfigMappingException, MissingValueException { try { - return type.cast(methodHandle.invoke(invokeParameter(config))); + return type.cast(methodHandle.invoke(List.of(invokeParameter(config)))); } catch (ConfigMappingException ex) { throw ex; } catch (Throwable ex) { @@ -62,7 +57,7 @@ class ObjectConfigMappers { } static class ConfigMethodHandleConfigMapper extends MethodHandleConfigMapper { - ConfigMethodHandleConfigMapper(Class type, String methodName, MethodHandle methodHandle) { + ConfigMethodHandleConfigMapper(Class type, String methodName, HelidonMethodHandle methodHandle) { super(type, methodName, methodHandle); } @@ -73,7 +68,7 @@ class ObjectConfigMappers { } static class StringMethodHandleConfigMapper extends MethodHandleConfigMapper { - StringMethodHandleConfigMapper(Class type, String methodName, MethodHandle methodHandle) { + StringMethodHandleConfigMapper(Class type, String methodName, HelidonMethodHandle methodHandle) { super(type, methodName, methodHandle); } @@ -176,10 +171,10 @@ class ObjectConfigMappers { static class GenericConfigMapper implements Function { private final Class type; - private final MethodHandle constructorHandle; + private final HelidonMethodHandle constructorHandle; private final Collection> propertyAccessors; - GenericConfigMapper(Class type, MethodHandle constructorHandle) { + GenericConfigMapper(Class type, HelidonMethodHandle constructorHandle) { this.type = type; this.constructorHandle = constructorHandle; @@ -192,7 +187,7 @@ class ObjectConfigMappers { @Override public T apply(Config config) throws ConfigMappingException, MissingValueException { try { - T instance = type.cast(constructorHandle.invoke()); + T instance = type.cast(constructorHandle.invoke(List.of())); for (PropertyAccessor propertyAccessor : propertyAccessors) { propertyAccessor.set(instance, config.get(propertyAccessor.name())); diff --git a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ReflectionUtil.java b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ReflectionUtil.java index 5c70ba577..92a8e8169 100644 --- a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ReflectionUtil.java +++ b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ReflectionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,6 @@ */ package io.helidon.config.objectmapping; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; @@ -76,11 +74,11 @@ final class ReflectionUtil { return (Class) REPLACED_TYPES.getOrDefault(type, type); } - static Optional findStaticMethod(Class type, String methodName, Class... parameterTypes) { + static Optional findStaticMethod(Class type, String methodName, Class... parameterTypes) { try { Method method = type.getMethod(methodName, parameterTypes); if (checkMethod(method, true, type, methodName, parameterTypes.length > 0)) { - return unreflect(method); + return Optional.of(HelidonMethodHandle.create(type, method)); } else { LOGGER.log(Level.FINEST, () -> "Class " + type.getName() + " method '" + methodName @@ -95,11 +93,11 @@ final class ReflectionUtil { return Optional.empty(); } - static Optional findConstructor(Class type, Class... parameterTypes) { + static Optional findConstructor(Class type, Class... parameterTypes) { try { Constructor constructor = type.getConstructor(parameterTypes); if (checkConstructor(constructor, parameterTypes.length > 0)) { - return Optional.of(MethodHandles.publicLookup().unreflectConstructor(constructor)); + return Optional.of(HelidonMethodHandle.create(type, constructor)); } else { LOGGER.log(Level.FINEST, () -> "Class " + type.getName() + " constructor with parameters " @@ -111,11 +109,6 @@ final class ReflectionUtil { ex, () -> "Class " + type.getName() + " does not have a constructor with parameters " + Arrays.toString(parameterTypes) + "."); - } catch (IllegalAccessException ex) { - LOGGER.log(Level.FINER, - ex, - () -> "Access checking fails on " + type.getName() - + " class, constructor with parameters " + Arrays.toString(parameterTypes) + "."); } return Optional.empty(); } @@ -125,14 +118,16 @@ final class ReflectionUtil { * e.g. Type t = Type.builder().build(); */ static Optional> findBuilderMethod(Class type) { - return findMethod(type, METHOD_BUILDER, true, null).flatMap( - builderMethod -> unreflect(builderMethod).flatMap( - builderHandler -> findBuilderBuildHandler(type, builderMethod.getReturnType()).map( - buildHandler -> - new BuilderAccessor<>(builderMethod.getReturnType(), - builderHandler, - type, - buildHandler)))); + return findMethod(type, METHOD_BUILDER, true, null) + .flatMap(builderMethod -> { + HelidonMethodHandle builderHandler = HelidonMethodHandle.create(type, builderMethod); + return findBuilderBuildHandler(type, builderMethod.getReturnType()).map( + buildHandler -> + new BuilderAccessor<>(builderMethod.getReturnType(), + builderHandler, + type, + buildHandler)); + }); } /** @@ -203,9 +198,9 @@ final class ReflectionUtil { } } - static Optional findBuilderBuildHandler(Class type, Class builderType) { + static Optional findBuilderBuildHandler(Class type, Class builderType) { return findMethod(builderType, METHOD_BUILD, false, type) - .flatMap(ReflectionUtil::unreflect); + .map(it -> HelidonMethodHandle.create(type, it)); } /** @@ -249,18 +244,6 @@ final class ReflectionUtil { return Optional.empty(); } - private static Optional unreflect(Method method) { - try { - return Optional.of(MethodHandles.publicLookup().unreflect(method)); - } catch (IllegalAccessException ex) { - LOGGER.log(Level.FINER, - ex, - () -> "Access checking fails on " + method.getDeclaringClass() + " class, method '" + method.getName() - + "' with parameters " + Arrays.asList(method.getParameters()) + "."); - } - return Optional.empty(); - } - /** * The class covers work with factory method. * @@ -268,11 +251,11 @@ final class ReflectionUtil { */ static class FactoryAccessor { private final Class type; - private final MethodHandle handle; + private final HelidonMethodHandle handle; private final LinkedHashMap> parameterValueProviders; FactoryAccessor(Class type, - MethodHandle handle, + HelidonMethodHandle handle, Parameter[] parameters) { this.type = type; this.handle = handle; @@ -284,7 +267,7 @@ final class ReflectionUtil { List args = createArguments(configNode); try { - Object obj = handle.invokeWithArguments(args); + Object obj = handle.invoke(args); return type.cast(obj); } catch (ConfigException ex) { throw ex; @@ -350,15 +333,15 @@ final class ReflectionUtil { */ static class BuilderAccessor { private final Class builderType; - private final MethodHandle builderHandler; + private final HelidonMethodHandle builderHandler; private final Class buildType; - private final MethodHandle buildHandler; + private final HelidonMethodHandle buildHandler; private final Collection> builderAccessors; BuilderAccessor(Class builderType, - MethodHandle builderHandler, + HelidonMethodHandle builderHandler, Class buildType, - MethodHandle buildHandler) { + HelidonMethodHandle buildHandler) { this.builderType = builderType; this.builderHandler = builderHandler; this.buildType = buildType; @@ -369,13 +352,13 @@ final class ReflectionUtil { public T create(Config config) { try { - Object builder = builderType.cast(builderHandler.invoke()); + Object builder = builderType.cast(builderHandler.invoke(List.of())); for (PropertyAccessor builderAccessor : builderAccessors) { builderAccessor.set(builder, config.get(builderAccessor.name())); } - return buildType.cast(buildHandler.invoke(builder)); + return buildType.cast(buildHandler.invoke(List.of(builder))); } catch (ConfigMappingException ex) { throw ex; } catch (Throwable ex) { @@ -393,14 +376,14 @@ final class ReflectionUtil { */ static class PropertyAccessor { private final String name; - private final MethodHandle handle; + private final HelidonMethodHandle handle; private final boolean hasValueAnnotation; private final PropertyWrapper propertyWrapper; PropertyAccessor(String name, Class propertyType, Class configAsType, boolean list, - MethodHandle handle, + HelidonMethodHandle handle, Value value) { this.name = name; this.handle = handle; @@ -432,7 +415,7 @@ final class ReflectionUtil { } } - MethodHandle handle() { + HelidonMethodHandle handle() { return handle; } @@ -555,30 +538,26 @@ final class ReflectionUtil { static PropertyAccessor createPropertyAccessor(Class type, String name, Method method) { - try { - final Class propertyType = (Class) method.getParameterTypes()[0]; - Class configAsType = propertyType; + final Class propertyType = (Class) method.getParameterTypes()[0]; + Class configAsType = propertyType; - boolean list = List.class.isAssignableFrom(configAsType); - if (list) { - Type genType = method.getGenericParameterTypes()[0]; - if (genType instanceof ParameterizedType) { - configAsType = (Class) ((ParameterizedType) genType).getActualTypeArguments()[0]; - } else { - throw new ConfigException("Unable to find generic type of List on setter parameter: " + method); - } + boolean list = List.class.isAssignableFrom(configAsType); + if (list) { + Type genType = method.getGenericParameterTypes()[0]; + if (genType instanceof ParameterizedType) { + configAsType = (Class) ((ParameterizedType) genType).getActualTypeArguments()[0]; + } else { + throw new ConfigException("Unable to find generic type of List on setter parameter: " + method); } - - MethodHandle handle = MethodHandles.publicLookup() - .findVirtual(type, - method.getName(), - MethodType.methodType(method.getReturnType(), method.getParameterTypes())); - - return new PropertyAccessor<>(name, propertyType, configAsType, list, handle, - method.getAnnotation(Value.class)); - } catch (NoSuchMethodException | IllegalAccessException | ClassCastException ex) { - throw new ConfigException("Cannot access setter: " + method, ex); } + + return new PropertyAccessor<>(name, + propertyType, + configAsType, + list, + HelidonMethodHandle.create(type, method), + method.getAnnotation(Value.class)); + } @SuppressWarnings("unchecked") @@ -599,12 +578,13 @@ final class ReflectionUtil { } } - MethodHandle handle = MethodHandles.publicLookup() - .findSetter(type, field.getName(), field.getType()); - - return new PropertyAccessor<>(name, propertyType, configAsType, list, handle, + return new PropertyAccessor<>(name, + propertyType, + configAsType, + list, + HelidonMethodHandle.create(type, field), field.getAnnotation(Value.class)); - } catch (NoSuchFieldException | IllegalAccessException | ClassCastException ex) { + } catch (ClassCastException ex) { throw new ConfigException("Cannot access field: " + field, ex); } } @@ -728,4 +708,116 @@ final class ReflectionUtil { this.defaultSupplier = defaultSupplier; } } + + static class FieldMethodHandle implements HelidonMethodHandle { + private final Field field; + private final Class type; + + FieldMethodHandle(Class type, Field field) { + this.type = type; + this.field = field; + } + + @Override + public Object invoke(List params) { + try { + field.set(params.get(0), params.get(1)); + return null; + } catch (IllegalAccessException e) { + throw new ConfigException("Field " + field + " is not accessible. Cannot set value", e); + } + } + + @Override + public MethodType type() { + return MethodType.methodType(Void.class, type, field.getType()); + } + } + + static class StaticMethodHandle implements HelidonMethodHandle { + private final Method method; + + StaticMethodHandle(Method method) { + this.method = method; + } + + @Override + public Object invoke(List params) { + try { + return method.invoke(null, params.toArray(new Object[0])); + } catch (IllegalAccessException e) { + throw new ConfigException("Method " + method + " is not accessible. Cannot invoke", e); + } catch (InvocationTargetException e) { + throw new ConfigException("Failed to invoke method " + method, e); + } + } + + @Override + public MethodType type() { + return MethodType.methodType(method.getReturnType(), method.getParameterTypes()); + } + } + + static class ConstructorMethodHandle implements HelidonMethodHandle { + private final Class type; + private final Constructor constructor; + + ConstructorMethodHandle(Class type, Constructor constructor) { + this.type = type; + this.constructor = constructor; + } + + @Override + public Object invoke(List params) { + try { + return constructor.newInstance(params.toArray(new Object[0])); + } catch (IllegalAccessException e) { + throw new ConfigException("Constructor " + constructor + " is not accessible. Cannot invoke", e); + } catch (InvocationTargetException e) { + throw new ConfigException("Failed to invoke constructor " + constructor, e); + } catch (InstantiationException e) { + throw new ConfigException("Failed to instantiate class using constructor " + constructor, e); + } catch (IllegalArgumentException e) { + throw new ConfigException("Parameters mismatch for constructor " + constructor, e); + } + } + + @Override + public MethodType type() { + return MethodType.methodType(type); + } + } + + public static class InstanceMethodHandle implements HelidonMethodHandle { + private Class type; + private final Method method; + + InstanceMethodHandle(Class type, Method method) { + this.type = type; + this.method = method; + } + + @Override + public Object invoke(List params) { + try { + // first is instance + Object instance = params.get(0); + List mutableParams = new ArrayList<>(params); + mutableParams.remove(0); + return method.invoke(params.get(0), mutableParams.toArray(new Object[0])); + } catch (IllegalAccessException e) { + throw new ConfigException("Method " + method + " is not accessible. Cannot invoke", e); + } catch (InvocationTargetException e) { + throw new ConfigException("Failed to invoke method " + method, e); + } + } + + @Override + public MethodType type() { + List> paramTypes = new ArrayList<>(); + paramTypes.add(type); + paramTypes.addAll(Arrays.asList(method.getParameterTypes())); + return MethodType.methodType(method.getReturnType(), paramTypes); + } + } } diff --git a/examples/quickstarts/helidon-quickstart-mp/pom.xml b/examples/quickstarts/helidon-quickstart-mp/pom.xml index 4c7ea4a34..abe4e2937 100644 --- a/examples/quickstarts/helidon-quickstart-mp/pom.xml +++ b/examples/quickstarts/helidon-quickstart-mp/pom.xml @@ -1,7 +1,7 @@ - org.eclipse - yasson - - org.eclipse yasson + runtime diff --git a/microprofile/security/src/main/resources/META-INF/helidon/native-image/weld-proxies.json b/microprofile/security/src/main/resources/META-INF/helidon/native-image/weld-proxies.json new file mode 100644 index 000000000..232c50fd1 --- /dev/null +++ b/microprofile/security/src/main/resources/META-INF/helidon/native-image/weld-proxies.json @@ -0,0 +1,14 @@ +[ + { + "bean-class": "io.helidon.security.SecurityContext", + "ifaces": [ + "io.helidon.security.SecurityContext" + ] + }, + { + "bean-class": "io.helidon.security.Security", + "ifaces": [ + "io.helidon.security.Security" + ] + } +] \ No newline at end of file diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml index 49e6e288a..be47d5982 100644 --- a/microprofile/tests/tck/pom.xml +++ b/microprofile/tests/tck/pom.xml @@ -1,7 +1,7 @@ - + diff --git a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java index 563e6b49c..f0de8bcb8 100644 --- a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java +++ b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java @@ -79,7 +79,7 @@ public final class Mp1Main { //Main.main(args); Server server = Server.builder() - .port(0) + .port(7001) .applications(new JaxRsApplicationNoCdi()) .retainDiscoveredApplications(true) .basePath("/cdi") diff --git a/tests/integration/native-image/mp-3/README.md b/tests/integration/native-image/mp-3/README.md new file mode 100644 index 000000000..8bd703493 --- /dev/null +++ b/tests/integration/native-image/mp-3/README.md @@ -0,0 +1,18 @@ +#GraalVM native image integration test MP-3 +_____ + +This is a manual (for the time being) test of integration with native-image. + +To run this test: + +```shell script +export GRAALVM_HOME=${path.to.graal.with.native-image} +mvn clean package -Pnative-image +./target/helidon-tests-native-image-mp-3 +``` + +Requires at least GraalVM 20.0.0 + +This test validates that Quickstart can run on native image with minimal number of changes - eventually with no changes. + + diff --git a/tests/integration/native-image/mp-3/pom.xml b/tests/integration/native-image/mp-3/pom.xml new file mode 100644 index 000000000..e35ca1258 --- /dev/null +++ b/tests/integration/native-image/mp-3/pom.xml @@ -0,0 +1,81 @@ + + + + + 4.0.0 + + io.helidon.applications + helidon-mp + 2.0.0-SNAPSHOT + ../../../../applications/mp/pom.xml + + io.helidon.tests.integration + helidon-tests-native-image-mp-3 + Helidon Integration Tests GraalVM Native image MP3 + + + This test uses quickstart application with minimal number of changes + + + + + io.helidon.microprofile.bundles + helidon-microprofile + + + org.jboss + jandex + runtime + true + + + javax.activation + javax.activation-api + runtime + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.jboss.jandex + jandex-maven-plugin + + + make-index + + + + + + diff --git a/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/GreetResource.java b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/GreetResource.java new file mode 100644 index 000000000..a667b183a --- /dev/null +++ b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/GreetResource.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.tests.integration.nativeimage.mp3; + +import java.util.Collections; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + +/** + * A simple JAX-RS resource to greet you. Examples: + * + * Get default greeting message: + * curl -X GET http://localhost:8080/greet + * + * Get greeting message for Joe: + * curl -X GET http://localhost:8080/greet/Joe + * + * Change greeting + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting + * + * The message is returned as a JSON object. + */ +@Path("/greet") +@RequestScoped +public class GreetResource { + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + /** + * The greeting message provider. + */ + private final GreetingProvider greetingProvider; + + /** + * Using constructor injection to get a configuration property. + * By default this gets the value from META-INF/microprofile-config + * + * @param greetingConfig the configured greeting message + */ + @Inject + public GreetResource(GreetingProvider greetingConfig) { + this.greetingProvider = greetingConfig; + } + + /** + * Return a wordly greeting message. + * + * @return {@link javax.json.JsonObject} + */ + @SuppressWarnings("checkstyle:designforextension") + @GET + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getDefaultMessage() { + return createResponse("World"); + } + + /** + * Return a greeting message using the name that was provided. + * + * @param name the name to greet + * @return {@link javax.json.JsonObject} + */ + @SuppressWarnings("checkstyle:designforextension") + @Path("/{name}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getMessage(@PathParam("name") String name) { + return createResponse(name); + } + + /** + * Set the greeting to use in future messages. + * + * @param jsonObject JSON containing the new greeting + * @return {@link javax.ws.rs.core.Response} + */ + @SuppressWarnings("checkstyle:designforextension") + @Path("/greeting") + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @RequestBody(name = "greeting", + required = true, + content = @Content(mediaType = "application/json", + schema = @Schema(type = SchemaType.STRING, example = "{\"greeting\" : \"Hola\"}"))) + @APIResponses({ + @APIResponse(name = "normal", responseCode = "204", description = "Greeting updated"), + @APIResponse(name = "missing 'greeting'", responseCode = "400", + description = "JSON did not contain setting for 'greeting'")}) + public Response updateGreeting(JsonObject jsonObject) { + + if (!jsonObject.containsKey("greeting")) { + JsonObject entity = JSON.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + return Response.status(Response.Status.BAD_REQUEST).entity(entity).build(); + } + + String newGreeting = jsonObject.getString("greeting"); + + greetingProvider.setMessage(newGreeting); + return Response.status(Response.Status.NO_CONTENT).build(); + } + + private JsonObject createResponse(String who) { + String msg = String.format("%s %s!", greetingProvider.getMessage(), who); + + return JSON.createObjectBuilder() + .add("message", msg) + .build(); + } +} diff --git a/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/GreetingProvider.java b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/GreetingProvider.java new file mode 100644 index 000000000..f21de8e99 --- /dev/null +++ b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/GreetingProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.nativeimage.mp3; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +/** + * Provider for greeting message. + */ +@ApplicationScoped +public class GreetingProvider { + private final AtomicReference message = new AtomicReference<>(); + + /** + * Create a new greeting provider, reading the message from configuration. + * + * @param message greeting to use + */ + @Inject + public GreetingProvider(@ConfigProperty(name = "app.greeting") String message) { + this.message.set(message); + } + + String getMessage() { + return message.get(); + } + + void setMessage(String message) { + this.message.set(message); + } +} diff --git a/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/ReactiveService.java b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/ReactiveService.java new file mode 100644 index 000000000..8614d1e6a --- /dev/null +++ b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/ReactiveService.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.nativeimage.mp3; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.helidon.microprofile.server.RoutingPath; +import io.helidon.security.Security; +import io.helidon.webserver.Routing; +import io.helidon.webserver.Service; + +/** + * Reactive webserver service. + * Supports only injection of {@link javax.enterprise.context.ApplicationScoped} beans. + */ +@ApplicationScoped +@RoutingPath("/reactive") +public class ReactiveService implements Service { + @Inject + private Security security; + + @Override + public void update(Routing.Rules rules) { + rules.get("/", (req, res) -> res.send("Security: " + security)); + } +} diff --git a/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/package-info.java b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/package-info.java new file mode 100644 index 000000000..fb858b4cb --- /dev/null +++ b/tests/integration/native-image/mp-3/src/main/java/io/helidon/tests/integration/nativeimage/mp3/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Quickstart MicroProfile example. + */ +package io.helidon.tests.integration.nativeimage.mp3; diff --git a/tests/integration/native-image/mp-3/src/main/resources/META-INF/beans.xml b/tests/integration/native-image/mp-3/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..00dc963f9 --- /dev/null +++ b/tests/integration/native-image/mp-3/src/main/resources/META-INF/beans.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/integrations/cdi/hibernate-cdi/src/main/resources/META-INF/services/io.helidon.common.spi.HelidonFeatureProvider b/tests/integration/native-image/mp-3/src/main/resources/META-INF/microprofile-config.properties similarity index 80% rename from integrations/cdi/hibernate-cdi/src/main/resources/META-INF/services/io.helidon.common.spi.HelidonFeatureProvider rename to tests/integration/native-image/mp-3/src/main/resources/META-INF/microprofile-config.properties index e06c61933..6ef8b91f6 100644 --- a/integrations/cdi/hibernate-cdi/src/main/resources/META-INF/services/io.helidon.common.spi.HelidonFeatureProvider +++ b/tests/integration/native-image/mp-3/src/main/resources/META-INF/microprofile-config.properties @@ -14,4 +14,10 @@ # limitations under the License. # -io.helidon.integrations.cdi.hibernate.HibernateFeatureProvider \ No newline at end of file +# Application properties. This is the default greeting +app.greeting=Hello + +# Microprofile server properties +#server.port=7001 +server.port=0 +server.host=0.0.0.0 diff --git a/tests/integration/native-image/mp-3/src/main/resources/logging.properties b/tests/integration/native-image/mp-3/src/main/resources/logging.properties new file mode 100644 index 000000000..8a2c2c1a1 --- /dev/null +++ b/tests/integration/native-image/mp-3/src/main/resources/logging.properties @@ -0,0 +1,37 @@ +# +# Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.common.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.microprofile.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO +#org.glassfish.jersey.level=INFO +#org.jboss.weld=INFO diff --git a/tests/integration/native-image/pom.xml b/tests/integration/native-image/pom.xml index a17c19307..44b95cad8 100644 --- a/tests/integration/native-image/pom.xml +++ b/tests/integration/native-image/pom.xml @@ -39,6 +39,7 @@ se-1 mp-1 mp-2 + mp-3