[GERONIMO-6798] custom annotation as marker (to reuse framework ones)
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/ArthurNativeImageExecutor.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/ArthurNativeImageExecutor.java
index af29f71..2826c0a 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/ArthurNativeImageExecutor.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/ArthurNativeImageExecutor.java
@@ -16,10 +16,17 @@
*/
package org.apache.geronimo.arthur.impl.nativeimage;
-import static java.util.Optional.ofNullable;
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.geronimo.arthur.impl.nativeimage.generator.ConfigurationGenerator;
+import org.apache.geronimo.arthur.impl.nativeimage.graal.CommandGenerator;
+import org.apache.geronimo.arthur.impl.nativeimage.process.ProcessExecutor;
+import org.apache.geronimo.arthur.spi.ArthurExtension;
import java.io.Writer;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Collection;
@@ -29,14 +36,7 @@
import java.util.function.BiConsumer;
import java.util.function.Function;
-import org.apache.geronimo.arthur.impl.nativeimage.generator.ConfigurationGenerator;
-import org.apache.geronimo.arthur.impl.nativeimage.graal.CommandGenerator;
-import org.apache.geronimo.arthur.impl.nativeimage.process.ProcessExecutor;
-import org.apache.geronimo.arthur.spi.ArthurExtension;
-
-import lombok.Builder;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
+import static java.util.Optional.ofNullable;
@Slf4j
@RequiredArgsConstructor
@@ -48,7 +48,8 @@
final ConfigurationGenerator configurationGenerator = new ConfigurationGenerator(
loadExtensions(),
configuration.configuration, configuration.workingDirectory, configuration.jsonSerializer,
- configuration.annotatedClassFinder, configuration.annotatedMethodFinder, configuration.implementationFinder,
+ configuration.annotatedClassFinder, configuration.annotatedFieldFinder,
+ configuration.annotatedMethodFinder, configuration.implementationFinder,
configuration.extensionProperties);
configurationGenerator.run();
@@ -72,6 +73,7 @@
private final Path workingDirectory;
private final Function<Class<? extends Annotation>, Collection<Class<?>>> annotatedClassFinder;
private final Function<Class<? extends Annotation>, Collection<Method>> annotatedMethodFinder;
+ private final Function<Class<? extends Annotation>, Collection<Field>> annotatedFieldFinder;
private final Function<Class<?>, Collection<Class<?>>> implementationFinder;
private final ArthurNativeImageConfiguration configuration;
private final Map<String, String> extensionProperties;
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
index c8a6b3f..a51903a 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
@@ -27,6 +27,7 @@
import java.io.IOException;
import java.io.Writer;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -59,11 +60,13 @@
private final Function<Class<? extends Annotation>, Collection<Class<?>>> classFinder;
private final Function<Class<?>, Collection<Class<?>>> implementationFinder;
private final Function<Class<? extends Annotation>, Collection<Method>> methodFinder;
+ private final Function<Class<? extends Annotation>, Collection<Field>> fieldFinder;
private final Map<String, String> extensionProperties;
public ConfigurationGenerator(final Iterable<ArthurExtension> extensions, final ArthurNativeImageConfiguration configuration,
final Path workingDirectory, final BiConsumer<Object, Writer> jsonSerializer,
final Function<Class<? extends Annotation>, Collection<Class<?>>> classFinder,
+ final Function<Class<? extends Annotation>, Collection<Field>> fieldFinder,
final Function<Class<? extends Annotation>, Collection<Method>> methodFinder,
final Function<Class<?>, Collection<Class<?>>> implementationFinder,
final Map<String, String> extensionProperties) {
@@ -72,6 +75,7 @@
this.workingDirectory = workingDirectory;
this.jsonSerializer = jsonSerializer;
this.classFinder = classFinder;
+ this.fieldFinder = fieldFinder;
this.methodFinder = methodFinder;
this.implementationFinder = implementationFinder;
this.extensionProperties = extensionProperties;
@@ -83,7 +87,7 @@
final HashMap<String, String> properties = ofNullable(this.extensionProperties).map(HashMap::new).orElseGet(HashMap::new);
properties.put("workingDirectory", workingDirectory.toAbsolutePath().toString());
- final DefautContext context = new DefautContext(configuration, classFinder, methodFinder, implementationFinder, properties);
+ final DefautContext context = new DefautContext(configuration, classFinder, methodFinder, fieldFinder, implementationFinder, properties);
for (final ArthurExtension extension : extensions) {
log.debug("Executing {}", extension);
context.setModified(false);
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
index 01dc457..8e7b44c 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
@@ -16,12 +16,12 @@
*/
package org.apache.geronimo.arthur.impl.nativeimage.generator;
-import static java.util.Arrays.asList;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
@@ -49,6 +49,7 @@
private final ArthurNativeImageConfiguration configuration;
private final Function<Class<? extends Annotation>, Collection<Class<?>>> annotatedClassesFinder;
private final Function<Class<? extends Annotation>, Collection<Method>> methodFinder;
+ private final Function<Class<? extends Annotation>, Collection<Field>> fieldFinder;
private final Function<Class<?>, Collection<Class<?>>> implementationFinder;
private final Collection<ClassReflectionModel> reflections = new HashSet<>();
private final Collection<ResourceModel> resources = new HashSet<>();
@@ -69,6 +70,11 @@
}
@Override
+ public <T extends Annotation> Collection<Field> findAnnotatedFields(final Class<T> annotation) {
+ return fieldFinder.apply(annotation);
+ }
+
+ @Override
public <T> Collection<Class<? extends T>> findImplementations(final Class<T> parent) {
return Collection.class.cast(implementationFinder.apply(parent));
}
@@ -127,7 +133,7 @@
@Override
public String getProperty(final String key) {
- return extensionProperties.get(key);
+ return extensionProperties == null ? null : extensionProperties.get(key);
}
@Override
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtension.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtension.java
index 2190de9..2d9c275 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtension.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtension.java
@@ -26,13 +26,18 @@
import org.apache.geronimo.arthur.spi.model.ResourceBundleModel;
import org.apache.geronimo.arthur.spi.model.ResourceModel;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
public class AnnotationExtension implements ArthurExtension {
@Override
@@ -42,6 +47,35 @@
@Override
public void execute(final Context context) {
+ ofNullable(context.getProperty("extension.annotation.custom.annotations.class"))
+ .ifPresent(names -> Stream.of(names.split(","))
+ .map(String::trim)
+ .filter(it -> !it.isEmpty())
+ .forEach(config -> { // syntax is: <annotation fqn>:<RegisterClass method=true|false>, if only fqn then all=true is supposed
+ final int sep = config.indexOf(':');
+ final String fqn = sep < 0 ? config : config.substring(0, sep);
+ final RegisterClass registerClass = newRegisterClass(sep < 0 ? "all=true" : config.substring(sep + 1));
+ context.findAnnotatedClasses(context.loadClass(fqn.trim()).asSubclass(Annotation.class)).stream()
+ .flatMap(clazz -> register(clazz, registerClass))
+ .forEach(context::register);
+ }));
+ ofNullable(context.getProperty("extension.annotation.custom.annotations.properties"))
+ .ifPresent(names -> Stream.of(names.split(","))
+ .map(String::trim)
+ .filter(it -> !it.isEmpty())
+ .forEach(config -> { // syntax is: <annotation fqn>:<RegisterClass method=true|false>, if only fqn then all=true is supposed
+ final int sep = config.indexOf(':');
+ final String fqn = sep < 0 ? config : config.substring(0, sep);
+ final RegisterClass registerClass = newRegisterClass(sep < 0 ? "all=true" : config.substring(sep + 1));
+ final Class<? extends Annotation> annot = context.loadClass(fqn.trim()).asSubclass(Annotation.class);
+ Stream.concat(
+ context.findAnnotatedMethods(annot).stream().map(Method::getDeclaringClass),
+ context.findAnnotatedFields(annot).stream().map(Field::getDeclaringClass))
+ .distinct()
+ .flatMap(clazz -> register(clazz, registerClass))
+ .forEach(context::register);
+ }));
+
context.findAnnotatedClasses(RegisterClass.class).stream()
.flatMap(clazz -> register(clazz, clazz.getAnnotation(RegisterClass.class)))
.forEach(context::register);
@@ -168,4 +202,62 @@
public boolean equals(final Object obj) {
return obj != null && AnnotationExtension.class == obj.getClass();
}
+
+ private RegisterClass newRegisterClass(final String inlineConf) {
+ final Map<String, Boolean> confs = Stream.of(inlineConf.split("\\|"))
+ .map(it -> it.split("="))
+ .collect(toMap(it -> it[0], it -> it.length < 2 || Boolean.parseBoolean(it[1])));
+ return new RegisterClass() {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return RegisterClass.class;
+ }
+
+ @Override
+ public boolean allDeclaredConstructors() {
+ return confs.getOrDefault("allDeclaredConstructors", false);
+ }
+
+ @Override
+ public boolean allPublicConstructors() {
+ return confs.getOrDefault("allPublicConstructors", false);
+ }
+
+ @Override
+ public boolean allDeclaredMethods() {
+ return confs.getOrDefault("allDeclaredMethods", false);
+ }
+
+ @Override
+ public boolean allPublicMethods() {
+ return confs.getOrDefault("allPublicMethods", false);
+ }
+
+ @Override
+ public boolean allDeclaredClasses() {
+ return confs.getOrDefault("allDeclaredClasses", false);
+ }
+
+ @Override
+ public boolean allPublicClasses() {
+ return confs.getOrDefault("allPublicClasses", false);
+ }
+
+ @Override
+ public boolean allDeclaredFields() {
+ return confs.getOrDefault("allDeclaredFields", false);
+ }
+
+ @Override
+ public boolean allPublicFields() {
+ return confs.getOrDefault("allPublicFields", false);
+ }
+
+ @Override
+ public boolean all() {
+ return confs.getOrDefault("all", false);
+ }
+ };
+ }
}
diff --git a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/CommandGeneratorTest.java b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/CommandGeneratorTest.java
index fef9fdc..774f39d 100644
--- a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/CommandGeneratorTest.java
+++ b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/CommandGeneratorTest.java
@@ -16,12 +16,12 @@
*/
package org.apache.geronimo.arthur.impl.nativeimage;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.singletonList;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import lombok.Data;
+import org.apache.geronimo.arthur.impl.nativeimage.generator.ConfigurationGenerator;
+import org.apache.geronimo.arthur.impl.nativeimage.graal.CommandGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import java.io.File;
import java.io.IOException;
@@ -33,15 +33,12 @@
import java.util.function.Function;
import java.util.stream.Stream;
-import org.apache.geronimo.arthur.impl.nativeimage.generator.ConfigurationGenerator;
-import org.apache.geronimo.arthur.impl.nativeimage.generator.DefautContext;
-import org.apache.geronimo.arthur.impl.nativeimage.graal.CommandGenerator;
-import org.apache.geronimo.arthur.spi.ArthurExtension;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import lombok.Data;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
class CommandGeneratorTest {
@Test
@@ -61,7 +58,7 @@
new ConfigurationGenerator(
singletonList(context -> context.registerGeneratedClass("org.foo.Bar", new byte[]{1, 2, 3})),
configuration, workingDirectory,
- (o, writer) -> {}, k -> emptyList(), k -> emptyList(),
+ (o, writer) -> {}, k -> emptyList(), k -> emptyList(), k -> emptyList(),
(Function<Class<?>, Collection<Class<?>>>) k -> emptyList(), emptyMap()
).run();
assertArrayEquals(new byte[]{1, 2, 3}, Files.readAllBytes(workingDirectory.resolve("dynamic_classes/org/foo/Bar.class")));
diff --git a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContextTest.java b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContextTest.java
index d77e626..c9d1064 100644
--- a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContextTest.java
+++ b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContextTest.java
@@ -50,7 +50,7 @@
})
void predicate(final String prop, final String value, final ArthurExtension.PredicateType type, final boolean result) {
final Optional<Predicate<String>> predicate = new DefautContext(
- new ArthurNativeImageConfiguration(), null, null, null,
+ new ArthurNativeImageConfiguration(), null, null, null, null,
"null".equals(prop) ? emptyMap() : singletonMap("includes", prop))
.createPredicate("includes", type);
assertEquals(result, !predicate.isPresent() || predicate.orElseThrow(IllegalStateException::new).test(value));
@@ -81,13 +81,13 @@
if (!"null".equals(excludesProp)) {
map.put("excludes", excludesProp);
}
- final DefautContext context = new DefautContext(new ArthurNativeImageConfiguration(), null, null, null, map);
+ final DefautContext context = new DefautContext(new ArthurNativeImageConfiguration(), null, null, null, null, map);
assertEquals(result, context.createIncludesExcludes("", type).test(value));
}
@Test
void findHierarchy() {
- final ArthurExtension.Context context = new DefautContext(new ArthurNativeImageConfiguration(), null, null, null, emptyMap());
+ final ArthurExtension.Context context = new DefautContext(new ArthurNativeImageConfiguration(), null, null, null, null, emptyMap());
assertEquals(singletonList(StandaloneClass.class), context.findHierarchy(StandaloneClass.class).collect(toList()));
assertEquals(asList(ChildClass.class, StandaloneClass.class), context.findHierarchy(ChildClass.class).collect(toList()));
assertEquals(asList(ImplClass.class, StandaloneInterface.class), context.findHierarchy(ImplClass.class).collect(toList()));
diff --git a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
index cdcf475..761bbbe 100644
--- a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
+++ b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
@@ -36,9 +36,14 @@
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyOrderStrategy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.util.Collection;
import java.util.Iterator;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.util.Collections.singletonMap;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
@@ -54,6 +59,7 @@
final DefautContext context = new DefautContext(new ArthurNativeImageConfiguration(),
finder::findAnnotatedClasses,
finder::findAnnotatedMethods,
+ finder::findAnnotatedFields,
p -> Collection.class.cast(finder.findImplementations(p)),
null);
new AnnotationExtension().execute(context);
@@ -103,6 +109,36 @@
}
}
+ @Test
+ void customAnnot() throws Exception {
+ final ClassesArchive archive = new ClassesArchive(MarkedRegister.class);
+ final AnnotationFinder finder = new AnnotationFinder(archive);
+ final DefautContext context = new DefautContext(new ArthurNativeImageConfiguration(),
+ finder::findAnnotatedClasses,
+ finder::findAnnotatedMethods,
+ finder::findAnnotatedFields,
+ p -> Collection.class.cast(finder.findImplementations(p)),
+ singletonMap("extension.annotation.custom.annotations.class", MyRegister.class.getName() + ":allDeclaredFields=true"));
+ new AnnotationExtension().execute(context);
+ try (final Jsonb jsonb = JsonbBuilder.create(
+ new JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) {
+ assertTrue(context.isModified());
+ final Iterator<ClassReflectionModel> reflections = context.getReflections().iterator();
+ assertTrue(reflections.hasNext());
+ assertEquals("{\"allDeclaredFields\":true,\"name\":\"org.apache.geronimo.arthur.impl.nativeimage.generator.extension.AnnotationExtensionTest$MarkedRegister\"}", jsonb.toJson(reflections.next()));
+ assertFalse(reflections.hasNext());
+ }
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public @interface MyRegister {
+ }
+
+ @MyRegister
+ public static class MarkedRegister {
+ }
+
@RegisterClasses.Entry(clazz = Filter.class)
@RegisterClasses.Entry(clazz = Files.class)
public static class OutProjectClassRegistration {
diff --git a/arthur-spi/src/main/java/org/apache/geronimo/arthur/spi/ArthurExtension.java b/arthur-spi/src/main/java/org/apache/geronimo/arthur/spi/ArthurExtension.java
index 270b09d..5a7eb78 100644
--- a/arthur-spi/src/main/java/org/apache/geronimo/arthur/spi/ArthurExtension.java
+++ b/arthur-spi/src/main/java/org/apache/geronimo/arthur/spi/ArthurExtension.java
@@ -17,6 +17,7 @@
package org.apache.geronimo.arthur.spi;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
@@ -67,6 +68,14 @@
<T extends Annotation> Collection<Method> findAnnotatedMethods(Class<T> annotation);
/**
+ * Simular to {@link #findAnnotatedMethods(Class)} but at field level.
+ * @param annotation the marker annotation to look for.
+ * @param <T> the annotation type.
+ * @return the list of methods with this annotation.
+ */
+ <T extends Annotation> Collection<Field> findAnnotatedFields(Class<T> annotation);
+
+ /**
* Find subclasses and implementation of a parent.
*
* @param parent the parent class to use as marker.
diff --git a/documentation/src/content/api.adoc b/documentation/src/content/api.adoc
index 41bfcf5..18df7b8 100644
--- a/documentation/src/content/api.adoc
+++ b/documentation/src/content/api.adoc
@@ -63,3 +63,12 @@
---
Previous: link:documentation.html[Documentation] Next: link:spi.html[Arthur SPI]
+
+== Custom annotation marker
+
+You can set `extension.annotation.custom.annotations.class` to a list of custom annotations set on classes to replace `RegisterClass`.
+Exact configuration value follows this pattern: `<custom annotation fully qualified name>[:[<RegisterClass attribute>=value]|*]?`.
+A common example is: `com.foo.MyAnnot1:all=true,com.foo.MyAnnot2:allPublicMethods=true|allPublicFields=true`.
+
+Similarly `extension.annotation.custom.annotations.properties` enables to configure the same thing but to find classes from marked fields or methods.
+It is very common with unmanaged `@ConfigProperty` classes for example.
diff --git a/knights/jsch-knight/src/test/java/org/apache/geronimo/arthur/knight/jsch/JschExtensionTest.java b/knights/jsch-knight/src/test/java/org/apache/geronimo/arthur/knight/jsch/JschExtensionTest.java
index 3458786..2d63594 100644
--- a/knights/jsch-knight/src/test/java/org/apache/geronimo/arthur/knight/jsch/JschExtensionTest.java
+++ b/knights/jsch-knight/src/test/java/org/apache/geronimo/arthur/knight/jsch/JschExtensionTest.java
@@ -34,7 +34,7 @@
@Test
void extension() {
final ArthurNativeImageConfiguration configuration = new ArthurNativeImageConfiguration();
- final DefautContext context = new DefautContext(configuration, null, null, null, null);
+ final DefautContext context = new DefautContext(configuration, null, null, null, null, null);
new JschExtension().execute(context);
assertTrue(configuration.isEnableAllSecurityServices());