diff --git a/api/pom.xml b/api/pom.xml index be94a16a..c8b2813c 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -57,14 +57,13 @@ jakarta.json-api - - io.smallrye jandex - test + + org.junit.jupiter junit-jupiter @@ -137,6 +136,12 @@ test + + ch.qos.logback + logback-classic + test + + diff --git a/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java index cb12403d..c050aa18 100644 --- a/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java +++ b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java @@ -75,7 +75,7 @@ public static SerializedDataType extractValue(final Class clasz, final String if (result.message() == null) { return (SerializedDataType) result.value(); } - throw new IllegalArgumentException(result.message()); + throw new IllegalArgumentException(result.message() + " (" + clasz + ")"); } } diff --git a/api/src/main/java/org/fuin/esc/api/JandexSerializedDataTypeRegistry.java b/api/src/main/java/org/fuin/esc/api/JandexSerializedDataTypeRegistry.java new file mode 100644 index 00000000..71494e95 --- /dev/null +++ b/api/src/main/java/org/fuin/esc/api/JandexSerializedDataTypeRegistry.java @@ -0,0 +1,101 @@ +package org.fuin.esc.api; + +import jakarta.validation.constraints.NotNull; +import org.fuin.utils4j.JandexIndexFileReader; +import org.fuin.utils4j.JandexUtils; +import org.jboss.jandex.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.lang.reflect.Modifier; +import java.util.*; + +/** + * Registry that is built up by scanning for classes that are annotated with {@link HasSerializedDataTypeConstant}. + * Inner classes are ignored. + */ +public class JandexSerializedDataTypeRegistry implements SerializedDataTypeRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(JandexSerializedDataTypeRegistry.class); + + private final SimpleSerializedDataTypeRegistry delegate; + + private final List classesDirs; + + private final List> classes; + + /** + * Default constructor. + */ + public JandexSerializedDataTypeRegistry() { + this(new File("target/classes")); + } + + /** + * Constructor with classes directories. Most likely only used in tests. + * + * @param classesDirs Directories with class files. + */ + public JandexSerializedDataTypeRegistry(final File... classesDirs) { + delegate = new SimpleSerializedDataTypeRegistry(); + this.classesDirs = Arrays.asList(classesDirs); + classes = scanForClasses(); + for (final Class domainEventClass : classes) { + delegate.add(serializedDataTypeConstant(domainEventClass), domainEventClass); + } + } + + @Override + @NotNull + public Class findClass(@NotNull SerializedDataType type) { + return delegate.findClass(type); + } + + /** + * Returns a list of known classes that can be serialized. + * + * @return SerializedDataType classes. + */ + public List> getClasses() { + return Collections.unmodifiableList(classes); + } + + private List> scanForClasses() { + final List indexes = new ArrayList<>(); + indexes.add(new JandexIndexFileReader.Builder().addDefaultResource().build().loadR()); + indexes.add(indexClassesDirs()); + return findClasses(CompositeIndex.create(indexes)); + } + + private IndexView indexClassesDirs() { + final Indexer indexer = new Indexer(); + final List knownClassFiles = new ArrayList<>(); + for (final File classesDir : classesDirs) { + JandexUtils.indexDir(indexer, knownClassFiles, classesDir); + } + return indexer.complete(); + } + + private static List> findClasses(final IndexView index) { + List> classes = new ArrayList<>(); + final Collection annotationInstances = index.getAnnotations(DotName.createSimple(HasSerializedDataTypeConstant.class)); + for (final AnnotationInstance annotationInstance : annotationInstances) { + final ClassInfo classInfo = annotationInstance.target().asClass(); + if (!Modifier.isAbstract(classInfo.flags()) + && !Modifier.isInterface(classInfo.flags()) + && !classInfo.name().toString().contains("$")) { + final Class clasz = JandexUtils.loadClass(classInfo.name()); + classes.add(clasz); + LOG.info("Added SerializedDataType to {}: {}", JandexSerializedDataTypeRegistry.class.getSimpleName(), clasz.getName()); + } + } + return classes; + } + + public SerializedDataType serializedDataTypeConstant(Class domainEventClass) { + final HasSerializedDataTypeConstant annotation = domainEventClass.getAnnotation(HasSerializedDataTypeConstant.class); + return HasSerializedDataTypeConstantValidator.extractValue(domainEventClass, annotation.value()); + } + +} diff --git a/api/src/test/java/org/fuin/esc/api/JandexSerializedDataTypeRegistryTest.java b/api/src/test/java/org/fuin/esc/api/JandexSerializedDataTypeRegistryTest.java new file mode 100644 index 00000000..1083f1e8 --- /dev/null +++ b/api/src/test/java/org/fuin/esc/api/JandexSerializedDataTypeRegistryTest.java @@ -0,0 +1,26 @@ +package org.fuin.esc.api; + +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the {@link JandexSerializedDataTypeRegistry} class. + */ +public class JandexSerializedDataTypeRegistryTest { + + @Test + public void testCreate() { + final JandexSerializedDataTypeRegistry testee = new JandexSerializedDataTypeRegistry(new File("target/test-classes")); + assertThat(testee.getClasses()).containsOnly(MyEvent.class); + } + + @Test + public void testFind() { + final JandexSerializedDataTypeRegistry testee = new JandexSerializedDataTypeRegistry(new File("target/test-classes")); + assertThat(testee.findClass(MyEvent.SER_TYPE)).isEqualTo(MyEvent.class); + } + +} diff --git a/api/src/test/java/org/fuin/esc/api/MyEvent.java b/api/src/test/java/org/fuin/esc/api/MyEvent.java index e59579b7..21dc7b9c 100644 --- a/api/src/test/java/org/fuin/esc/api/MyEvent.java +++ b/api/src/test/java/org/fuin/esc/api/MyEvent.java @@ -4,8 +4,11 @@ /** * Example event. */ +@HasSerializedDataTypeConstant public class MyEvent { + public static final SerializedDataType SER_TYPE = new SerializedDataType("MyEvent"); + private String name; public MyEvent(final String name) { diff --git a/api/src/test/resources/logback-test.xml b/api/src/test/resources/logback-test.xml new file mode 100644 index 00000000..6062cc03 --- /dev/null +++ b/api/src/test/resources/logback-test.xml @@ -0,0 +1,18 @@ + + + + + + %d{yyyy.dd.MM HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 619c7cf8..63c9649e 100644 --- a/pom.xml +++ b/pom.xml @@ -325,6 +325,12 @@ 4.0.1 + + io.smallrye + jandex + 3.1.2 + + diff --git a/spi/src/main/java/org/fuin/esc/spi/Base64Data.java b/spi/src/main/java/org/fuin/esc/spi/Base64Data.java index 2e8ca4d2..3c0679f0 100644 --- a/spi/src/main/java/org/fuin/esc/spi/Base64Data.java +++ b/spi/src/main/java/org/fuin/esc/spi/Base64Data.java @@ -32,6 +32,7 @@ /** * Contains some Base64 encoded data. */ +@HasSerializedDataTypeConstant @XmlRootElement(name = Base64Data.EL_ROOT_NAME) public final class Base64Data implements ToJsonCapable { diff --git a/spi/src/main/java/org/fuin/esc/spi/EscEvent.java b/spi/src/main/java/org/fuin/esc/spi/EscEvent.java index 6f99e5ea..18cedf2f 100644 --- a/spi/src/main/java/org/fuin/esc/spi/EscEvent.java +++ b/spi/src/main/java/org/fuin/esc/spi/EscEvent.java @@ -39,6 +39,7 @@ /** * An event structure. */ +@HasSerializedDataTypeConstant @XmlRootElement(name = EscEvent.EL_ROOT_NAME) public final class EscEvent implements ToJsonCapable { diff --git a/spi/src/main/java/org/fuin/esc/spi/EscEvents.java b/spi/src/main/java/org/fuin/esc/spi/EscEvents.java index f73a9385..6db7917c 100644 --- a/spi/src/main/java/org/fuin/esc/spi/EscEvents.java +++ b/spi/src/main/java/org/fuin/esc/spi/EscEvents.java @@ -40,6 +40,7 @@ /** * A list of events. */ +@HasSerializedDataTypeConstant @XmlRootElement(name = EscEvents.EL_ROOT_NAME) public final class EscEvents implements ToJsonCapable { diff --git a/spi/src/main/java/org/fuin/esc/spi/EscMeta.java b/spi/src/main/java/org/fuin/esc/spi/EscMeta.java index f60d5e86..fc3ba014 100644 --- a/spi/src/main/java/org/fuin/esc/spi/EscMeta.java +++ b/spi/src/main/java/org/fuin/esc/spi/EscMeta.java @@ -40,6 +40,7 @@ /** * A structure that contains the user's meta data and the system's meta information. */ +@HasSerializedDataTypeConstant @XmlRootElement(name = EscMeta.EL_ROOT_NAME) public final class EscMeta implements ToJsonCapable { diff --git a/spi/src/test/java/org/fuin/esc/spi/JandexSerializedDataTypeRegistryTest.java b/spi/src/test/java/org/fuin/esc/spi/JandexSerializedDataTypeRegistryTest.java new file mode 100644 index 00000000..2a269603 --- /dev/null +++ b/spi/src/test/java/org/fuin/esc/spi/JandexSerializedDataTypeRegistryTest.java @@ -0,0 +1,26 @@ +package org.fuin.esc.spi; + +import org.fuin.esc.api.JandexSerializedDataTypeRegistry; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the {@link JandexSerializedDataTypeRegistry} class. + */ +public class JandexSerializedDataTypeRegistryTest { + + @Test + public void testBaseDataTypes() { + final JandexSerializedDataTypeRegistry testee = new JandexSerializedDataTypeRegistry(); + assertThat(testee.getClasses()).containsOnly( + Base64Data.class, + EscEvent.class, + EscEvents.class, + EscMeta.class + ); + } + +}