You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pp...@apache.org on 2020/03/26 20:43:35 UTC
[camel-quarkus] branch master updated: Fix #253 Build time property
to register classes for reflection
This is an automated email from the ASF dual-hosted git repository.
ppalaga pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/master by this push:
new 11d0386 Fix #253 Build time property to register classes for reflection
11d0386 is described below
commit 11d0386514cc9909491f985a0bb3e5b27de7db44
Author: Peter Palaga <pp...@redhat.com>
AuthorDate: Thu Mar 26 16:10:02 2020 +0100
Fix #253 Build time property to register classes for reflection
---
docs/modules/ROOT/pages/native-mode.adoc | 37 +++++++++++++
.../core/deployment/NativeImageProcessor.java | 37 ++++++++++++-
.../quarkus/core/deployment/util/PathFilter.java | 16 ++++++
.../core/deployment/util/PathFilterTest.java | 32 +++++++++++
.../org/apache/camel/quarkus/core/CamelConfig.java | 62 ++++++++++++++++++++++
integration-tests/core/pom.xml | 4 ++
.../apache/camel/quarkus/core/CoreResource.java | 42 +++++++++++++++
.../core/src/main/resources/application.properties | 7 +++
.../java/org/apache/camel/quarkus/core/CoreIT.java | 27 ++++++++++
.../org/apache/camel/quarkus/core/CoreTest.java | 26 +++++++++
10 files changed, 289 insertions(+), 1 deletion(-)
diff --git a/docs/modules/ROOT/pages/native-mode.adoc b/docs/modules/ROOT/pages/native-mode.adoc
index e90cc8a..449b3c0 100644
--- a/docs/modules/ROOT/pages/native-mode.adoc
+++ b/docs/modules/ROOT/pages/native-mode.adoc
@@ -28,12 +28,49 @@ in Quarkus documentation.
== Embedding resource in native executable
Resources needed at runtime need to be explicitly embedded in the built native executable. In such situations, the `include-patterns` and `exclude-patterns` configurations could be set in `application.properties` as demonstrated below:
+
[source,properties]
----
quarkus.camel.native.resources.include-patterns = docs/*,images/*
quarkus.camel.native.resources.exclude-patterns = docs/ignored.adoc,images/ignored.png
----
+
In the example above, resources named _docs/included.adoc_ and _images/included.png_ would be embedded in the native executable while _docs/ignored.adoc_ and _images/ignored.png_ would not.
`include-patterns` and `exclude-patterns` are list of comma separated link:https://github.com/apache/camel/blob/master/core/camel-util/src/main/java/org/apache/camel/util/AntPathMatcher.java[Ant-path style patterns].
At the end of the day, resources matching `include-patterns` are marked for inclusion at the exception of resources matching `exclude-patterns`.
+
+[[reflection]]
+== Registering classes for reflection
+
+By default, dynamic reflection is not available in native mode. Classes for which reflective access is needed have to be
+registered for reflection at compile time.
+
+In many cases, application developers do not need to care because Quarkus extensions are able to detect the classes that
+require the reflection and register them automatically.
+
+However, in some situations Quarkus extensions may miss some classes and it is up to the application developer to
+register them. There are two ways to do that:
+
+1. The `https://quarkus.io/guides/writing-native-applications-tips#alternative-with-registerforreflection[@io.quarkus.runtime.annotations.RegisterForReflection]`
+annotation can be used to register classes on which it is used, or it can also register third party classes via
+its `targets` attribute.
+
+2. The `quarkus.camel.native.reflection` options in `application.properties`:
++
+[source,properties]
+----
+quarkus.camel.native.reflection.include-patterns = org.apache.commons.lang3.tuple.*
+quarkus.camel.native.reflection.exclude-patterns = org.apache.commons.lang3.tuple.*Triple
+----
++
+For these options to work properly, the artifacts containing the selected classes
+must either contain a Jandex index ({@code META-INF/jandex.idx}) or they must
+be registered for indexing using the {@code quarkus.index-dependency.*} options
+in {@code application.properties} - e.g.
++
+[source,properties]
+----
+quarkus.index-dependency.commons-lang3.group-id = org.apache.commons
+quarkus.index-dependency.commons-lang3.artifact-id = commons-lang3
+----
diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/NativeImageProcessor.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/NativeImageProcessor.java
index 9f28703..89ce069 100644
--- a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/NativeImageProcessor.java
+++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/NativeImageProcessor.java
@@ -44,6 +44,7 @@ import org.apache.camel.impl.engine.DefaultComponentResolver;
import org.apache.camel.impl.engine.DefaultDataFormatResolver;
import org.apache.camel.impl.engine.DefaultLanguageResolver;
import org.apache.camel.quarkus.core.CamelConfig;
+import org.apache.camel.quarkus.core.CamelConfig.ReflectionConfig;
import org.apache.camel.quarkus.core.CamelConfig.ResourcesConfig;
import org.apache.camel.quarkus.core.Flags;
import org.apache.camel.quarkus.core.deployment.util.PathFilter;
@@ -63,7 +64,7 @@ import org.slf4j.LoggerFactory;
import static org.apache.commons.lang3.ClassUtils.getPackageName;
-class NativeImageProcessor {
+public class NativeImageProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(NativeImageProcessor.class);
/*
@@ -240,6 +241,40 @@ class NativeImageProcessor {
});
}
}
+
+ @BuildStep
+ void reflection(CamelConfig config, ApplicationArchivesBuildItem archives,
+ BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {
+
+ final ReflectionConfig reflectionConfig = config.native_.reflection;
+ if (!reflectionConfig.includePatterns.isPresent()) {
+ LOGGER.debug("No classes registered for reflection via quarkus.camel.native.reflection.include-patterns");
+ return;
+ }
+
+ LOGGER.debug("Scanning resources for native inclusion from include-patterns {}",
+ reflectionConfig.includePatterns.get());
+
+ final PathFilter.Builder builder = new PathFilter.Builder();
+ reflectionConfig.includePatterns.map(list -> list.stream()).orElseGet(Stream::empty)
+ .map(className -> className.replace('.', '/'))
+ .forEach(pathPattern -> builder.include(pathPattern));
+ reflectionConfig.excludePatterns.map(list -> list.stream()).orElseGet(Stream::empty)
+ .map(className -> className.replace('.', '/'))
+ .forEach(pathPattern -> builder.exclude(pathPattern));
+ final PathFilter pathFilter = builder.build();
+
+ for (ApplicationArchive archive : archives.getAllApplicationArchives()) {
+ LOGGER.debug("Scanning resources for native inclusion from archive at {}", archive.getArchiveLocation());
+
+ final Path rootPath = archive.getArchiveRoot();
+ String[] selectedClassNames = pathFilter.scanClassNames(rootPath, CamelSupport.safeWalk(rootPath),
+ Files::isRegularFile);
+ if (selectedClassNames.length > 0) {
+ reflectiveClasses.produce(new ReflectiveClassBuildItem(true, true, selectedClassNames));
+ }
+ }
+ }
}
/*
diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/util/PathFilter.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/util/PathFilter.java
index 4c6772d..4813bed 100644
--- a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/util/PathFilter.java
+++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/util/PathFilter.java
@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
+import java.util.stream.Stream;
import org.apache.camel.util.AntPathMatcher;
import org.apache.camel.util.ObjectHelper;
@@ -31,6 +32,9 @@ import org.jboss.jandex.DotName;
* A utility able to filter resource paths using Ant-like includes and excludes.
*/
public class PathFilter {
+ private static final String CLASS_SUFFIX = ".class";
+ private static final int CLASS_SUFFIX_LENGTH = CLASS_SUFFIX.length();
+
private final AntPathMatcher matcher = new AntPathMatcher();
private final List<String> includePatterns;
private final List<String> excludePatterns;
@@ -83,6 +87,18 @@ public class PathFilter {
}
}
+ public String[] scanClassNames(Path rootPath, Stream<Path> pathStream, Predicate<Path> isRegularFile) {
+ return pathStream
+ .filter(isRegularFile)
+ .filter(path -> path.getFileName().toString().endsWith(CLASS_SUFFIX))
+ .map(filePath -> rootPath.relativize(filePath))
+ .map(relPath -> relPath.toString())
+ .map(stringPath -> stringPath.substring(0, stringPath.length() - CLASS_SUFFIX_LENGTH))
+ .filter(stringPredicate)
+ .map(slashClassName -> slashClassName.replace('/', '.'))
+ .toArray(String[]::new);
+ }
+
static String sanitize(String path) {
path = path.trim();
return (!path.isEmpty() && path.charAt(0) == '/')
diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/util/PathFilterTest.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/util/PathFilterTest.java
index 2956860..133d7b6 100644
--- a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/util/PathFilterTest.java
+++ b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/deployment/util/PathFilterTest.java
@@ -21,8 +21,10 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
+import java.util.stream.Stream;
import org.jboss.jandex.DotName;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -96,4 +98,34 @@ public class PathFilterTest {
return new PathFilter(includePatterns, excludePatterns).asStringPredicate().test(path);
}
+ @Test
+ void scanClassNames() {
+ final PathFilter filter = new PathFilter.Builder()
+ .include("org/p1/*")
+ .include("org/p2/**")
+ .exclude("org/p1/ExcludedClass")
+ .exclude("org/p2/excludedpackage/**")
+ .build();
+ final Path rootPath = Paths.get("/foo");
+ final Stream<Path> pathStream = Stream.of(
+ "org/p1/Class1.class",
+ "org/p1/Class1$Inner.class",
+ "org/p1/Class1.txt",
+ "org/p1/ExcludedClass.class",
+ "org/p2/excludedpackage/ExcludedClass.class",
+ "org/p2/excludedpackage/p/ExcludedClass.class",
+ "org/p2/whatever/Class2.class")
+ .map(rootPath::resolve);
+
+ final Predicate<Path> isRegularFile = path -> path.getFileName().toString().contains(".");
+ final String[] classNames = filter.scanClassNames(rootPath, pathStream, isRegularFile);
+
+ Assertions.assertArrayEquals(new String[] {
+ "org.p1.Class1",
+ "org.p1.Class1$Inner",
+ "org.p2.whatever.Class2"
+ }, classNames);
+
+ }
+
}
diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelConfig.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelConfig.java
index 37ec208..b8fa70b 100644
--- a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelConfig.java
+++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/CamelConfig.java
@@ -194,6 +194,12 @@ public class CamelConfig {
@ConfigItem
public ResourcesConfig resources;
+ /**
+ * Register classes for reflection.
+ */
+ @ConfigItem
+ public ReflectionConfig reflection;
+
}
@ConfigGroup
@@ -226,6 +232,62 @@ public class CamelConfig {
}
@ConfigGroup
+ public static class ReflectionConfig {
+
+ /**
+ * A comma separated list of Ant-path style patterns to match class names
+ * that should be <strong>excluded</strong> from registering for reflection.
+ * Use the class name format as returned by the {@code java.lang.Class.getName()}
+ * method: package segments delimited by period {@code .} and inner classes
+ * by dollar sign {@code $}.
+ * <p>
+ * This option narrows down the set selected by {@link #includePatterns}.
+ * By default, no classes are excluded.
+ * <p>
+ * This option cannot be used to unregister classes which have been registered
+ * internally by Quarkus extensions.
+ */
+ @ConfigItem
+ public Optional<List<String>> excludePatterns;
+
+ /**
+ * A comma separated list of Ant-path style patterns to match class names
+ * that should be registered for reflection.
+ * Use the class name format as returned by the {@code java.lang.Class.getName()}
+ * method: package segments delimited by period {@code .} and inner classes
+ * by dollar sign {@code $}.
+ * <p>
+ * By default, no classes are included. The set selected by this option can be
+ * narrowed down by {@link #excludePatterns}.
+ * <p>
+ * Note that Quarkus extensions typically register the required classes for
+ * reflection by themselves. This option is useful in situations when the
+ * built in functionality is not sufficient.
+ * <p>
+ * Note that this option enables the full reflective access for constructors,
+ * fields and methods. If you need a finer grained control, consider using
+ * <code>io.quarkus.runtime.annotations.RegisterForReflection</code> annotation
+ * in your Java code.
+ * <p>
+ * For this option to work properly, the artifacts containing the selected classes
+ * must either contain a Jandex index ({@code META-INF/jandex.idx}) or they must
+ * be registered for indexing using the {@code quarkus.index-dependency.*} family
+ * of options in {@code application.properties} - e.g.
+ *
+ * <pre>
+ * quarkus.index-dependency.my-dep.group-id = org.my-group
+ * quarkus.index-dependency.my-dep.artifact-id = my-artifact
+ * </pre>
+ *
+ * where {@code my-dep} is a label of your choice to tell Quarkus that
+ * {@code org.my-group} and with {@code my-artifact} belong together.
+ */
+ @ConfigItem
+ public Optional<List<String>> includePatterns;
+
+ }
+
+ @ConfigGroup
public static class RuntimeCatalogConfig {
/**
* Used to control the resolution of components catalog info.
diff --git a/integration-tests/core/pom.xml b/integration-tests/core/pom.xml
index f090a34..599e87f 100644
--- a/integration-tests/core/pom.xml
+++ b/integration-tests/core/pom.xml
@@ -77,6 +77,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
<!-- test dependencies -->
<dependency>
diff --git a/integration-tests/core/src/main/java/org/apache/camel/quarkus/core/CoreResource.java b/integration-tests/core/src/main/java/org/apache/camel/quarkus/core/CoreResource.java
index 9e58def..d5a1e74 100644
--- a/integration-tests/core/src/main/java/org/apache/camel/quarkus/core/CoreResource.java
+++ b/integration-tests/core/src/main/java/org/apache/camel/quarkus/core/CoreResource.java
@@ -18,6 +18,9 @@ package org.apache.camel.quarkus.core;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import javax.enterprise.context.ApplicationScoped;
@@ -29,6 +32,7 @@ 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.apache.camel.CamelContext;
import org.apache.camel.ExtendedCamelContext;
@@ -167,4 +171,42 @@ public class CoreResource {
return IOUtils.toString(is, StandardCharsets.UTF_8);
}
}
+
+ @Path("/reflection/{className}/method/{methodName}/{value}")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response reflectMethod(@PathParam("className") String className,
+ @PathParam("methodName") String methodName,
+ @PathParam("value") String value) {
+ try {
+ final Class<?> cl = Class.forName(className);
+ final Object inst = cl.newInstance();
+ final Method method = cl.getDeclaredMethod(methodName, Object.class);
+ method.invoke(inst, value);
+ return Response.ok(inst.toString()).build();
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException
+ | SecurityException | IllegalArgumentException | InvocationTargetException e) {
+ return Response.serverError().entity(e.getClass().getName() + ": " + e.getMessage()).build();
+ }
+ }
+
+ @Path("/reflection/{className}/field/{fieldName}/{value}")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response reflectField(@PathParam("className") String className,
+ @PathParam("fieldName") String fieldName,
+ @PathParam("value") String value) {
+ try {
+ final Class<?> cl = Class.forName(className);
+ final Object inst = cl.newInstance();
+ final Field field = cl.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(inst, value);
+ return Response.ok(inst.toString()).build();
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchFieldException
+ | SecurityException | IllegalArgumentException e) {
+ return Response.serverError().entity(e.getClass().getName() + ": " + e.getMessage()).build();
+ }
+ }
+
}
diff --git a/integration-tests/core/src/main/resources/application.properties b/integration-tests/core/src/main/resources/application.properties
index 699294e..a03920c 100644
--- a/integration-tests/core/src/main/resources/application.properties
+++ b/integration-tests/core/src/main/resources/application.properties
@@ -28,6 +28,13 @@ quarkus.camel.runtime-catalog.languages = false
quarkus.camel.native.resources.include-patterns = include-pattern-folder/*
quarkus.camel.native.resources.exclude-patterns = exclude-pattern-folder/*,include-pattern-folder/excluded.txt
+
+# declarative reflection
+quarkus.index-dependency.commons-lang3.group-id = org.apache.commons
+quarkus.index-dependency.commons-lang3.artifact-id = commons-lang3
+quarkus.camel.native.reflection.include-patterns = org.apache.commons.lang3.tuple.*
+quarkus.camel.native.reflection.exclude-patterns = org.apache.commons.lang3.tuple.*Triple
+
#
# Camel
#
diff --git a/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreIT.java b/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreIT.java
index 2996e5b..a631c88 100644
--- a/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreIT.java
+++ b/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreIT.java
@@ -20,6 +20,7 @@ import io.quarkus.test.junit.NativeImageTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -56,4 +57,30 @@ public class CoreIT extends CoreTest {
RestAssured.when().get("/test/resources/no-pattern-folder/excluded.properties.txt").then().assertThat()
.statusCode(204);
}
+
+ @Test
+ void reflectiveMethod() {
+ RestAssured.when()
+ .get(
+ "/test/reflection/{className}/method/{methodName}/{value}",
+ "org.apache.commons.lang3.tuple.MutableTriple",
+ "setLeft",
+ "Kermit")
+ .then()
+ .statusCode(500) // *Triple is excluded in application.properties, but 500 will happen only in native mode
+ .body(is("java.lang.ClassNotFoundException: org.apache.commons.lang3.tuple.MutableTriple"));
+ }
+
+ @Test
+ void reflectiveField() {
+ RestAssured.when()
+ .get(
+ "/test/reflection/{className}/field/{fieldName}/{value}",
+ "org.apache.commons.lang3.tuple.MutableTriple",
+ "left",
+ "Joe")
+ .then()
+ .statusCode(500) // *Triple is excluded in application.properties, but 500 will happen only in native mode
+ .body(is("java.lang.ClassNotFoundException: org.apache.commons.lang3.tuple.MutableTriple"));
+ }
}
diff --git a/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreTest.java b/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreTest.java
index 34977cd..46fac1a 100644
--- a/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreTest.java
+++ b/integration-tests/core/src/test/java/org/apache/camel/quarkus/core/CoreTest.java
@@ -76,4 +76,30 @@ public class CoreTest {
public void testLRUCacheFactory() {
RestAssured.when().get("/test/lru-cache-factory").then().body(is(DefaultLRUCacheFactory.class.getName()));
}
+
+ @Test
+ void reflectiveMethod() {
+ RestAssured.when()
+ .get(
+ "/test/reflection/{className}/method/{methodName}/{value}",
+ "org.apache.commons.lang3.tuple.MutablePair",
+ "setLeft",
+ "Kermit")
+ .then()
+ .statusCode(200)
+ .body(is("(Kermit,null)"));
+ }
+
+ @Test
+ void reflectiveField() {
+ RestAssured.when()
+ .get(
+ "/test/reflection/{className}/field/{fieldName}/{value}",
+ "org.apache.commons.lang3.tuple.MutablePair",
+ "left",
+ "Joe")
+ .then()
+ .statusCode(200)
+ .body(is("(Joe,null)"));
+ }
}