diff --git a/pom.xml b/pom.xml index 20bc82c..df58042 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ org.fuin units4j - 0.11.0 + 0.12.0-SNAPSHOT test @@ -206,6 +206,20 @@ jacoco-maven-plugin + + io.smallrye + jandex-maven-plugin + 3.0.6 + + + make-index + + jandex + + + + + diff --git a/src/main/java/org/fuin/cqrs4j/JandexDomainEventRegistry.java b/src/main/java/org/fuin/cqrs4j/JandexDomainEventRegistry.java new file mode 100644 index 0000000..c9bc0af --- /dev/null +++ b/src/main/java/org/fuin/cqrs4j/JandexDomainEventRegistry.java @@ -0,0 +1,117 @@ +package org.fuin.cqrs4j; + +import jakarta.validation.constraints.NotNull; +import org.fuin.ddd4j.ddd.DomainEvent; +import org.fuin.ddd4j.ddd.JandexEntityIdFactory; +import org.fuin.esc.api.*; +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} and implement {@link DomainEvent}, + * but not {@link AggregateCommand}. + */ +public class JandexDomainEventRegistry implements SerializedDataTypeRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(JandexDomainEventRegistry.class); + + private static final DotName COMMAND_INTERFACE = DotName.createSimple(Command.class); + + private final SimpleSerializedDataTypeRegistry delegate; + + private final List classesDirs; + + private final List> domainEventClasses; + + /** + * Default constructor. + */ + public JandexDomainEventRegistry() { + this(new File("target/classes")); + } + + /** + * Constructor with classes directories. Most likely only used in tests. + * + * @param classesDirs Directories with class files. + */ + public JandexDomainEventRegistry(final File... classesDirs) { + delegate = new SimpleSerializedDataTypeRegistry(); + this.classesDirs = Arrays.asList(classesDirs); + domainEventClasses = scanForDomainEventClasses(); + for (final Class domainEventClass : domainEventClasses) { + delegate.add(serializedDataTypeConstant(domainEventClass), domainEventClass); + } + } + + @Override + @NotNull + public Class findClass(@NotNull SerializedDataType type) { + return delegate.findClass(type); + } + + /** + * Returns a list of known {@link DomainEvent} classes. + * + * @return Domain event classes. + */ + public List> getDomainEventClasses() { + return Collections.unmodifiableList(domainEventClasses); + } + + private List> scanForDomainEventClasses() { + final List indexes = new ArrayList<>(); + indexes.add(new JandexIndexFileReader.Builder().addDefaultResource().build().loadR()); + indexes.add(indexClassesDirs()); + return findDomainEventClasses(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> findDomainEventClasses(final IndexView index) { + List> classes = new ArrayList<>(); + final Collection classInfos = index.getAllKnownImplementors(DotName.createSimple(DomainEvent.class)); + for (final ClassInfo classInfo : classInfos) { + if (!Modifier.isAbstract(classInfo.flags()) + && !Modifier.isInterface(classInfo.flags())) { + + final Class clasz = JandexUtils.loadClass(classInfo.name()); + if (!AggregateCommand.class.isAssignableFrom(clasz)) { + boolean include = true; + if (clasz.getAnnotation(HasSerializedDataTypeConstant.class) == null) { + LOG.warn("Missing annotation @{} on {} class: {}", HasSerializedDataTypeConstant.class.getSimpleName(), DomainEvent.class.getSimpleName(), clasz.getName()); + include = false; + } + if (include) { + classes.add(clasz); + LOG.info("Added {} class to {}: {}", DomainEvent.class.getSimpleName(), JandexEntityIdFactory.class.getSimpleName(), clasz.getName()); + } else { + LOG.debug("Ignored {} class: {}", DomainEvent.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/src/test/java/org/fuin/cqrs4j/ACreatedEvent.java b/src/test/java/org/fuin/cqrs4j/ACreatedEvent.java new file mode 100644 index 0000000..9e56c2e --- /dev/null +++ b/src/test/java/org/fuin/cqrs4j/ACreatedEvent.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. + * http://www.fuin.org/ + *

+ * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + *

+ * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j; + +import org.fuin.ddd4j.ddd.AbstractDomainEvent; +import org.fuin.ddd4j.ddd.EntityIdPath; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.api.HasSerializedDataTypeConstant; +import org.fuin.esc.api.SerializedDataType; +import org.fuin.esc.api.TypeName; + +@HasSerializedDataTypeConstant +public class ACreatedEvent extends AbstractDomainEvent { + + private static final long serialVersionUID = 1L; + + /** Unique name of the event. */ + public static final TypeName TYPE = new TypeName("ACreatedEvent"); + + /** Unique name of the serialized event. */ + public static final SerializedDataType SER_TYPE = new SerializedDataType(TYPE.asBaseType()); + + private static final EventType EVENT_TYPE = new EventType(TYPE.asBaseType()); + + private AId id; + + public ACreatedEvent(final AId id) { + super(new EntityIdPath(id)); + this.id = id; + } + + public AId getId() { + return id; + } + + @Override + public EventType getEventType() { + return EVENT_TYPE; + } + +} diff --git a/src/test/java/org/fuin/cqrs4j/JandexDomainEventRegistryTest.java b/src/test/java/org/fuin/cqrs4j/JandexDomainEventRegistryTest.java new file mode 100644 index 0000000..b68baf5 --- /dev/null +++ b/src/test/java/org/fuin/cqrs4j/JandexDomainEventRegistryTest.java @@ -0,0 +1,27 @@ +package org.fuin.cqrs4j; + +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the {@link JandexDomainEventRegistry} class. + */ +public class JandexDomainEventRegistryTest { + + @Test + public void testCreate() { + final JandexDomainEventRegistry testee = new JandexDomainEventRegistry(new File("target/test-classes")); + assertThat(testee.getDomainEventClasses()).containsOnly(ACreatedEvent.class); + } + + @Test + public void testFind() { + final JandexDomainEventRegistry testee = new JandexDomainEventRegistry(new File("target/test-classes")); + assertThat(testee.findClass(ACreatedEvent.SER_TYPE)).isEqualTo(ACreatedEvent.class); + + } + +}