diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java index 501bd0f2a..fb773a0a4 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java @@ -676,12 +676,39 @@ public int compareTo(ApplicationModule o) { private List findPublishedEvents() { DescribedPredicate isEvent = implement(JMoleculesTypes.DOMAIN_EVENT) // - .or(isAnnotatedWith(JMoleculesTypes.AT_DOMAIN_EVENT)); + .or(isAnnotatedInTypeHierarchyWith(JMoleculesTypes.AT_DOMAIN_EVENT)); return classes.that(isEvent).stream() // .map(EventType::new).toList(); } + /** + * Returns a {@link DescribedPredicate} matching types that are annotated or meta-annotated with the given + * annotation type, either directly or anywhere in their type hierarchy (superclasses and implemented interfaces). + * + * @param annotationType must not be {@literal null} or empty. + * @return will never be {@literal null}. + */ + private static DescribedPredicate isAnnotatedInTypeHierarchyWith(String annotationType) { + + Assert.hasText(annotationType, "Annotation type must not be null or empty!"); + + return new DescribedPredicate<>("is annotated with %s directly or in its type hierarchy", annotationType) { + + @Override + public boolean test(JavaClass type) { + + return isAnnotated(type) || Stream + .concat(type.getAllRawSuperclasses().stream(), type.getAllRawInterfaces().stream()) + .anyMatch(this::isAnnotated); + } + + private boolean isAnnotated(JavaClass type) { + return type.isAnnotatedWith(annotationType) || type.isMetaAnnotatedWith(annotationType); + } + }; + } + /** * Returns a {@link Stream} of all super types of the given one that are declared in the same module as well as the * type itself. diff --git a/spring-modulith-core/src/test/java/com/acme/withatbean/TestEvents.java b/spring-modulith-core/src/test/java/com/acme/withatbean/TestEvents.java index 41e53b0f0..a3a875dc3 100644 --- a/spring-modulith-core/src/test/java/com/acme/withatbean/TestEvents.java +++ b/spring-modulith-core/src/test/java/com/acme/withatbean/TestEvents.java @@ -63,4 +63,11 @@ public static JMoleculesAnnotated of() { } public static class JMoleculesImplementing implements org.jmolecules.event.types.DomainEvent {} + + @org.jmolecules.event.annotation.DomainEvent + public interface JMoleculesAnnotatedInterface {} + + public static class JMoleculesImplementingAnnotatedInterface implements JMoleculesAnnotatedInterface {} + + public static class JMoleculesExtendingAnnotatedClass extends JMoleculesAnnotated {} } diff --git a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModuleUnitTest.java b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModuleUnitTest.java index f3d6a2914..c2b296721 100644 --- a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModuleUnitTest.java +++ b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModuleUnitTest.java @@ -31,7 +31,10 @@ import com.acme.withatbean.SampleAggregate; import com.acme.withatbean.TestEvents.JMoleculesAnnotated; +import com.acme.withatbean.TestEvents.JMoleculesAnnotatedInterface; +import com.acme.withatbean.TestEvents.JMoleculesExtendingAnnotatedClass; import com.acme.withatbean.TestEvents.JMoleculesImplementing; +import com.acme.withatbean.TestEvents.JMoleculesImplementingAnnotatedInterface; import com.tngtech.archunit.core.domain.JavaClass; /** @@ -60,11 +63,17 @@ void discoversPublishedEvents() { JavaClass jMoleculesAnnotated = classes.getRequiredClass(JMoleculesAnnotated.class); JavaClass jMoleculesImplementing = classes.getRequiredClass(JMoleculesImplementing.class); + JavaClass jMoleculesAnnotatedInterface = classes.getRequiredClass(JMoleculesAnnotatedInterface.class); + JavaClass jMoleculesImplementingAnnotatedInterface = classes + .getRequiredClass(JMoleculesImplementingAnnotatedInterface.class); + JavaClass jMoleculesExtendingAnnotatedClass = classes + .getRequiredClass(JMoleculesExtendingAnnotatedClass.class); List events = module.getPublishedEvents(); assertThat(events.stream().map(EventType::getType)) // - .containsExactlyInAnyOrder(jMoleculesAnnotated, jMoleculesImplementing); + .containsExactlyInAnyOrder(jMoleculesAnnotated, jMoleculesImplementing, jMoleculesAnnotatedInterface, + jMoleculesImplementingAnnotatedInterface, jMoleculesExtendingAnnotatedClass); assertThat(events.stream().filter(it -> it.getType().equals(jMoleculesAnnotated))) // .element(0) // .satisfies(it -> {