Fixes for MP quickstart in native image. (#1564)

* Fixes for MP quickstart in native image.
* Mp Quicstart update - fix injection and documentation.

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
This commit is contained in:
Tomas Langer
2020-03-24 12:54:13 +01:00
committed by GitHub
parent 631c8a3ac6
commit e9774cdb9f
27 changed files with 735 additions and 205 deletions

View File

@@ -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)

View File

@@ -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<Object> 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));
}
}

View File

@@ -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<Class<?>, Function<Config, ?>> mappers() {
return Map.of();
@@ -110,7 +114,7 @@ public class ObjectConfigMapperProvider implements ConfigMapperProvider {
private static <T> Optional<Function<Config, T>> findStaticStringMethodMapper(Class<T> type,
String methodName) {
Optional<MethodHandle> method = findStaticMethod(type,
Optional<HelidonMethodHandle> method = findStaticMethod(type,
methodName,
String.class);

View File

@@ -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<T, P> implements Function<Config, T> {
private final Class<T> type;
private final String methodName;
private final MethodHandle methodHandle;
private final HelidonMethodHandle methodHandle;
MethodHandleConfigMapper(Class<T> type, String methodName, MethodHandle methodHandle) {
MethodHandleConfigMapper(Class<T> 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<T> extends MethodHandleConfigMapper<T, Config> {
ConfigMethodHandleConfigMapper(Class<T> type, String methodName, MethodHandle methodHandle) {
ConfigMethodHandleConfigMapper(Class<T> type, String methodName, HelidonMethodHandle methodHandle) {
super(type, methodName, methodHandle);
}
@@ -73,7 +68,7 @@ class ObjectConfigMappers {
}
static class StringMethodHandleConfigMapper<T> extends MethodHandleConfigMapper<T, String> {
StringMethodHandleConfigMapper(Class<T> type, String methodName, MethodHandle methodHandle) {
StringMethodHandleConfigMapper(Class<T> type, String methodName, HelidonMethodHandle methodHandle) {
super(type, methodName, methodHandle);
}
@@ -176,10 +171,10 @@ class ObjectConfigMappers {
static class GenericConfigMapper<T> implements Function<Config, T> {
private final Class<T> type;
private final MethodHandle constructorHandle;
private final HelidonMethodHandle constructorHandle;
private final Collection<PropertyAccessor<?>> propertyAccessors;
GenericConfigMapper(Class<T> type, MethodHandle constructorHandle) {
GenericConfigMapper(Class<T> 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()));

View File

@@ -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<T>) REPLACED_TYPES.getOrDefault(type, type);
}
static Optional<MethodHandle> findStaticMethod(Class<?> type, String methodName, Class<?>... parameterTypes) {
static Optional<HelidonMethodHandle> 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<MethodHandle> findConstructor(Class<?> type, Class<?>... parameterTypes) {
static Optional<HelidonMethodHandle> 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 <T> Optional<BuilderAccessor<T>> findBuilderMethod(Class<T> 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 <T> Optional<MethodHandle> findBuilderBuildHandler(Class<T> type, Class<?> builderType) {
static <T> Optional<HelidonMethodHandle> findBuilderBuildHandler(Class<T> 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<MethodHandle> 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<T> {
private final Class<T> type;
private final MethodHandle handle;
private final HelidonMethodHandle handle;
private final LinkedHashMap<String, PropertyWrapper<?>> parameterValueProviders;
FactoryAccessor(Class<T> type,
MethodHandle handle,
HelidonMethodHandle handle,
Parameter[] parameters) {
this.type = type;
this.handle = handle;
@@ -284,7 +267,7 @@ final class ReflectionUtil {
List<Object> 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<T> {
private final Class<?> builderType;
private final MethodHandle builderHandler;
private final HelidonMethodHandle builderHandler;
private final Class<T> buildType;
private final MethodHandle buildHandler;
private final HelidonMethodHandle buildHandler;
private final Collection<PropertyAccessor<?>> builderAccessors;
BuilderAccessor(Class<?> builderType,
MethodHandle builderHandler,
HelidonMethodHandle builderHandler,
Class<T> 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<T> {
private final String name;
private final MethodHandle handle;
private final HelidonMethodHandle handle;
private final boolean hasValueAnnotation;
private final PropertyWrapper<T> propertyWrapper;
PropertyAccessor(String name, Class<T> 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 <T> PropertyAccessor<T> createPropertyAccessor(Class<T> type,
String name,
Method method) {
try {
final Class<T> propertyType = (Class<T>) method.getParameterTypes()[0];
Class<?> configAsType = propertyType;
final Class<T> propertyType = (Class<T>) 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<Object> 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<Object> 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<Object> 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<Object> params) {
try {
// first is instance
Object instance = params.get(0);
List<Object> 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<Class<?>> paramTypes = new ArrayList<>();
paramTypes.add(type);
paramTypes.addAll(Arrays.asList(method.getParameterTypes()));
return MethodType.methodType(method.getReturnType(), paramTypes);
}
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
@@ -31,19 +31,11 @@
<artifactId>helidon-quickstart-mp</artifactId>
<name>Helidon Quickstart MP Example</name>
<properties>
<mainClass>io.helidon.examples.quickstart.mp.Main</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>

View File

@@ -1,70 +0,0 @@
/*
* 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.examples.quickstart.mp;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import io.helidon.microprofile.server.Server;
/**
* The application main class.
*/
public final class Main {
/**
* Cannot be instantiated.
*/
private Main() {
}
/**
* Application main entry point.
* @param args command line arguments
* @throws IOException if there are problems reading logging properties
*/
public static void main(final String[] args) throws IOException {
// load logging configuration
setupLogging();
// start the server
Server server = startServer();
System.out.println("http://localhost:" + server.port() + "/greet");
}
/**
* Start the server.
* @return the created {@link Server} instance
*/
static Server startServer() {
// Server will automatically pick up configuration from
// microprofile-config.properties
// and Application classes annotated as @ApplicationScoped
return Server.create().start();
}
/**
* Configure logging from logging.properties file.
*/
private static void setupLogging() throws IOException {
try (InputStream is = Main.class.getResourceAsStream("/logging.properties")) {
LogManager.getLogManager().readConfiguration(is);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 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.
@@ -19,21 +19,24 @@ import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import io.helidon.microprofile.server.RoutingPath;
import io.helidon.security.SecurityContext;
import io.helidon.security.Security;
import io.helidon.webserver.Routing;
import io.helidon.webserver.Service;
/**
* TODO javadoc.
* Reactive service.
* <p>
* Helidon WebServer reactive services can be used in MP as well.
* Injection is limited to {@link javax.enterprise.context.ApplicationScoped}.
*/
@ApplicationScoped
@RoutingPath("/reactive")
public class ReactiveService implements Service {
@Inject
private SecurityContext securityContext;
private Security security;
@Override
public void update(Routing.Rules rules) {
rules.get("/", (req, res) -> res.send("Context: " + securityContext));
rules.get("/", (req, res) -> res.send("Security: " + security));
}
}

View File

@@ -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.
@@ -37,7 +37,7 @@ class MainTest {
@BeforeAll
public static void startTheServer() throws Exception {
server = Main.startServer();
server = Server.create().start();
}
@Test

View File

@@ -102,17 +102,17 @@ Start the application:
Build the "jlink" Docker Image
```
docker build -t helidon-quickstart-se-jlink -f Dockerfile.jlink .
docker build -t helidon-quickstart-mp-jlink -f Dockerfile.jlink .
```
Start the application:
```
docker run --rm -p 8080:8080 helidon-quickstart-se-jlink:latest
docker run --rm -p 8080:8080 helidon-quickstart-mp-jlink:latest
```
See the start script help:
```
docker run --rm helidon-quickstart-se-jlink:latest --help
docker run --rm helidon-quickstart-mp-jlink:latest --help
```

View File

@@ -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.
@@ -17,6 +17,8 @@
package io.helidon.health.checks;
import java.lang.management.ThreadMXBean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@@ -37,6 +39,8 @@ import org.eclipse.microprofile.health.Liveness;
@ApplicationScoped // this will be ignored if not within CDI
@BuiltInHealthCheck
public final class DeadlockHealthCheck implements HealthCheck {
private static final Logger LOGGER = Logger.getLogger(DeadlockHealthCheck.class.getName());
/**
* Used for detecting deadlocks. Injected in the constructor.
*/
@@ -61,8 +65,14 @@ public final class DeadlockHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
// Thanks to https://stackoverflow.com/questions/1102359/programmatic-deadlock-detection-in-java#1102410
boolean noDeadLock = (threadBean.findDeadlockedThreads() == null);
boolean noDeadLock = false;
try {
// Thanks to https://stackoverflow.com/questions/1102359/programmatic-deadlock-detection-in-java#1102410
noDeadLock = (threadBean.findDeadlockedThreads() == null);
} catch (Throwable e) {
// ThreadBean does not work - probably in native image
LOGGER.log(Level.FINEST, "Failed to find deadlocks in ThreadMXBean, ignoring this healthcheck", e);
}
return HealthCheckResponse.named("deadlock")
.state(noDeadLock)
.build();

View File

@@ -203,7 +203,7 @@ public final class HealthSupport implements Service {
private HcResponse callHealthChecks(HealthCheck hc) {
try {
return new HcResponse(hc.call());
} catch (Exception e) {
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to compute health check for " + hc.getClass().getName(), e);
return new HcResponse(HealthCheckResponse

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2019, 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.
@@ -78,17 +78,11 @@
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
<scope>runtime</scope>
<exclusions>
<exclusion>
<!-- Jersey depends on an older version -->
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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"
]
}
]

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
@@ -35,7 +35,6 @@
<module>tck-health</module>
<module>tck-metrics</module>
<module>tck-messaging</module>
<module>tck-fault-tolerance</module>
<module>tck-jwt-auth</module>
<module>tck-openapi</module>
<module>tck-opentracing</module>
@@ -68,4 +67,13 @@
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>tck-ft</id>
<modules>
<module>tck-fault-tolerance</module>
</modules>
</profile>
</profiles>
</project>

View File

@@ -2,7 +2,7 @@
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<!--
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.
@@ -21,11 +21,11 @@
<!--
- We run these manually as some tests can easily fail on a loaded
- system like those in our CI/CD pipeline.
- No longer commented out - use 'tck-ft' profile to run these tests
-->
<test name="microprofile-fault-tolerance 2.0 TCK">
<packages>
<!-- package name="org.eclipse.microprofile.fault.tolerance.tck.*">
</package -->
<package name="org.eclipse.microprofile.fault.tolerance.tck.*"/>
</packages>
</test>
</suite>

View File

@@ -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")

View File

@@ -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.

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2019, 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-mp</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../../../../applications/mp/pom.xml</relativePath>
</parent>
<groupId>io.helidon.tests.integration</groupId>
<artifactId>helidon-tests-native-image-mp-3</artifactId>
<name>Helidon Integration Tests GraalVM Native image MP3</name>
<description>
This test uses quickstart application with minimal number of changes
</description>
<dependencies>
<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile</artifactId>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-libs</id>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<executions>
<execution>
<id>make-index</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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();
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0"
bean-discovery-mode="annotated">
</beans>

View File

@@ -14,4 +14,10 @@
# limitations under the License.
#
io.helidon.integrations.cdi.hibernate.HibernateFeatureProvider
# 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

View File

@@ -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

View File

@@ -39,6 +39,7 @@
<module>se-1</module>
<module>mp-1</module>
<module>mp-2</module>
<module>mp-3</module>
</modules>
</project>