From eadd2dab833067012bd417f71635d29f0205e58f Mon Sep 17 00:00:00 2001 From: Timon Back Date: Fri, 17 Nov 2023 16:05:58 +0100 Subject: [PATCH 1/4] feat: ignore listeners methods introduced by java generic type erasure --- .../AsyncAnnotationScannerUtil.java | 24 ++++++++++ .../AsyncListenerAnnotationScanner.java | 3 +- .../AsyncPublisherAnnotationScanner.java | 3 +- .../AsyncAnnotationScannerUtilTest.java | 34 ++++++++++++++ ...tenerAnnotationScannerIntegrationTest.java | 45 ++++++++++++++++++- ...isherAnnotationScannerIntegrationTest.java | 43 ++++++++++++++++++ 6 files changed, 149 insertions(+), 3 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java index 82a4da2b9..c159503c6 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java @@ -124,6 +124,30 @@ private static Message parseMessage(AsyncOperation asyncOperation) { return messageBuilder.build(); } + /** + * Checks whether the method is an actual method on the class + *
+ * When generic are used in interfaces, after type erasure class will have an additional method (with object as parameter) + * + * @param method The method in question + * @param type The original class, which may implement interface + * @return true, when the method was created due to type erasure + */ + public static boolean isMethodInherited(Method method, Class type) { + List methodsFromInterfaces = Arrays.stream(type.getInterfaces()) + .flatMap((c) -> Arrays.stream(c.getMethods())) + .toList(); + + for (Method methodFromInterface : methodsFromInterfaces) { + if (method.getName().equals(methodFromInterface.getName()) + && method.getReturnType().equals(methodFromInterface.getReturnType()) + && Arrays.equals(method.getParameterTypes(), methodFromInterface.getParameterTypes())) { + return true; + } + } + return false; + } + /** * extracts servers array from the given AsyncOperation, resolves placeholdes with spring variables and * return a List of server names. diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java index d6821f053..9223438b0 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java @@ -67,7 +67,8 @@ private Stream getAnnotatedMethods(Class type) { return Arrays.stream(type.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(annotationClass) - || method.isAnnotationPresent(annotationClassRepeatable)); + || method.isAnnotationPresent(annotationClassRepeatable)) + .filter(method -> !AsyncAnnotationScannerUtil.isMethodInherited(method, type)); } private Stream toOperationData(Method method) { diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java index 596547404..8e4817e31 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java @@ -67,7 +67,8 @@ private Stream getAnnotatedMethods(Class type) { return Arrays.stream(type.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(annotationClass) - || method.isAnnotationPresent(annotationClassRepeatable)); + || method.isAnnotationPresent(annotationClassRepeatable)) + .filter(method -> !AsyncAnnotationScannerUtil.isMethodInherited(method, type)); } private Stream toOperationData(Method method) { diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java index 539c264a7..cf2c07e4d 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -147,6 +148,23 @@ void processMessageFromAnnotationWithAsyncMessage(Class classWithOperationBin assertEquals(expectedMessage, message); } + @Test + void isMethodInInterfaces() throws NoSuchMethodException { + // given + Class clazz = ClassImplementingInterface.class; + var expectedMethod = clazz.getDeclaredMethod("methodFromInterface", String.class); + var methodAfterTypeErasureOfInterface = clazz.getDeclaredMethod("methodFromInterface", Object.class); + + // when + then + assertThat(expectedMethod).isNotNull(); + assertThat(methodAfterTypeErasureOfInterface).isNotNull(); + + assertThat(AsyncAnnotationScannerUtil.isMethodInherited(expectedMethod, clazz)) + .isFalse(); + assertThat(AsyncAnnotationScannerUtil.isMethodInherited(methodAfterTypeErasureOfInterface, clazz)) + .isTrue(); + } + @Test void getServers() throws NoSuchMethodException { Method m = ClassWithOperationBindingProcessor.class.getDeclaredMethod("methodWithAnnotation", String.class); @@ -258,4 +276,20 @@ private static class ClassWithMultipleOperationBindingProcessors { @TestAbstractOperationBindingProcessor.TestOperationBinding() private void methodWithAnnotation(String payload) {} } + + private static class ClassImplementingInterface implements ClassInterface { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "${test.property.test-channel}", + description = "${test.property.description}")) + @TestOperationBindingProcessor.TestOperationBinding() + @Override + public void methodFromInterface(String payload) {} + } + + public static interface ClassInterface { + void methodFromInterface(T payload); + } } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java index 17f03974e..99d827f69 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java @@ -54,7 +54,7 @@ "test.property.server1=server1", "test.property.server2=server2" }) -class AsyncListenerAnnotationScannerTest { +class AsyncListenerAnnotationScannerIntegrationTest { @Autowired private AsyncListenerAnnotationScanner channelScanner; @@ -243,6 +243,38 @@ void scan_componentHasAsyncMethodAnnotation() { assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } + @Test + void scan_componentHasOnlyDeclaredMethods() { + // Given a class with a method, which is declared in a generic interface + setClassToScan(ClassImplementingInterface.class); + + // When scan is called + Map actualChannels = channelScanner.scan(); + + // Then the returned collection contains the channel with the actual method, excluding type erased methods + Message message = Message.builder() + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .description(null) + .payload(PayloadReference.fromModelName(String.class.getSimpleName())) + .schemaFormat("application/vnd.oai.openapi+json;version=3.0.0") + .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .bindings(EMPTY_MAP) + .build(); + + Operation operation = Operation.builder() + .description("test channel operation description") + .operationId("test-channel_publish") + .bindings(EMPTY_MAP) + .message(message) + .build(); + + ChannelItem expectedChannel = + ChannelItem.builder().bindings(null).publish(operation).build(); + + assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); + } + private static class ClassWithoutListenerAnnotation { private void methodWithoutAnnotation() {} @@ -319,6 +351,17 @@ private void methodWithAnnotation(SimpleFoo payload) {} private void methodWithoutAnnotation() {} } + private static class ClassImplementingInterface implements AsyncAnnotationScannerUtilTest.ClassInterface { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + @Override + public void methodFromInterface(String payload) {} + } + @Data @NoArgsConstructor @Schema(description = "SimpleFoo Message Description") diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java index 1c16ec570..72b21fc29 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java @@ -205,6 +205,38 @@ void scan_componentHasPublisherMethodWithAsyncMessageAnnotation() { assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } + @Test + void scan_componentHasOnlyDeclaredMethods() { + // Given a class with a method, which is declared in a generic interface + setClassToScan(ClassImplementingInterface.class); + + // When scan is called + Map actualChannels = channelScanner.scan(); + + // Then the returned collection contains the channel with the actual method, excluding type erased methods + Message message = Message.builder() + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .description(null) + .payload(PayloadReference.fromModelName(String.class.getSimpleName())) + .schemaFormat("application/vnd.oai.openapi+json;version=3.0.0") + .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .bindings(EMPTY_MAP) + .build(); + + Operation operation = Operation.builder() + .description("test channel operation description") + .operationId("test-channel_subscribe") + .bindings(EMPTY_MAP) + .message(message) + .build(); + + ChannelItem expectedChannel = + ChannelItem.builder().bindings(null).subscribe(operation).build(); + + assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); + } + private static class ClassWithoutPublisherAnnotation { private void methodWithoutAnnotation() {} @@ -244,6 +276,17 @@ private static class ClassWithMultipleListenerAnnotations { private void methodWithMultipleAnnotation(SimpleFoo payload) {} } + private static class ClassImplementingInterface implements AsyncAnnotationScannerUtilTest.ClassInterface { + + @AsyncPublisher( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + @Override + public void methodFromInterface(String payload) {} + } + @Data @NoArgsConstructor private static class SimpleFoo { From 93d4226ec9943c9dca169f1f6dab8e24ab4f8adc Mon Sep 17 00:00:00 2001 From: Timon Back Date: Fri, 17 Nov 2023 17:29:37 +0100 Subject: [PATCH 2/4] feat: use isBridge method (wip) --- .../AsyncAnnotationScannerUtil.java | 24 ------------- .../AsyncListenerAnnotationScanner.java | 4 +-- .../AsyncPublisherAnnotationScanner.java | 7 ++-- .../AsyncAnnotationScannerUtilTest.java | 34 ------------------- ...tenerAnnotationScannerIntegrationTest.java | 31 ++++++++++++++--- ...isherAnnotationScannerIntegrationTest.java | 30 +++++++++++++--- 6 files changed, 59 insertions(+), 71 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java index c159503c6..82a4da2b9 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtil.java @@ -124,30 +124,6 @@ private static Message parseMessage(AsyncOperation asyncOperation) { return messageBuilder.build(); } - /** - * Checks whether the method is an actual method on the class - *
- * When generic are used in interfaces, after type erasure class will have an additional method (with object as parameter) - * - * @param method The method in question - * @param type The original class, which may implement interface - * @return true, when the method was created due to type erasure - */ - public static boolean isMethodInherited(Method method, Class type) { - List methodsFromInterfaces = Arrays.stream(type.getInterfaces()) - .flatMap((c) -> Arrays.stream(c.getMethods())) - .toList(); - - for (Method methodFromInterface : methodsFromInterfaces) { - if (method.getName().equals(methodFromInterface.getName()) - && method.getReturnType().equals(methodFromInterface.getReturnType()) - && Arrays.equals(method.getParameterTypes(), methodFromInterface.getParameterTypes())) { - return true; - } - } - return false; - } - /** * extracts servers array from the given AsyncOperation, resolves placeholdes with spring variables and * return a List of server names. diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java index 9223438b0..8e0f8599b 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java @@ -66,9 +66,9 @@ private Stream getAnnotatedMethods(Class type) { log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName()); return Arrays.stream(type.getDeclaredMethods()) + .filter(method -> !method.isBridge()) .filter(method -> method.isAnnotationPresent(annotationClass) - || method.isAnnotationPresent(annotationClassRepeatable)) - .filter(method -> !AsyncAnnotationScannerUtil.isMethodInherited(method, type)); + || method.isAnnotationPresent(annotationClassRepeatable)); } private Stream toOperationData(Method method) { diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java index 8e4817e31..b1bf8af90 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java @@ -66,9 +66,10 @@ private Stream getAnnotatedMethods(Class type) { log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName()); return Arrays.stream(type.getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(annotationClass) - || method.isAnnotationPresent(annotationClassRepeatable)) - .filter(method -> !AsyncAnnotationScannerUtil.isMethodInherited(method, type)); + .filter(method -> !method.isBridge()) + .filter(method ->org.springframework.core.annotation.AnnotationsUtils.getAnnotation(method, annotationClass) != null); +// .filter(method -> method.isAnnotationPresent(annotationClass) +// || method.isAnnotationPresent(annotationClassRepeatable)); } private Stream toOperationData(Method method) { diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java index cf2c07e4d..539c264a7 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncAnnotationScannerUtilTest.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Map; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -148,23 +147,6 @@ void processMessageFromAnnotationWithAsyncMessage(Class classWithOperationBin assertEquals(expectedMessage, message); } - @Test - void isMethodInInterfaces() throws NoSuchMethodException { - // given - Class clazz = ClassImplementingInterface.class; - var expectedMethod = clazz.getDeclaredMethod("methodFromInterface", String.class); - var methodAfterTypeErasureOfInterface = clazz.getDeclaredMethod("methodFromInterface", Object.class); - - // when + then - assertThat(expectedMethod).isNotNull(); - assertThat(methodAfterTypeErasureOfInterface).isNotNull(); - - assertThat(AsyncAnnotationScannerUtil.isMethodInherited(expectedMethod, clazz)) - .isFalse(); - assertThat(AsyncAnnotationScannerUtil.isMethodInherited(methodAfterTypeErasureOfInterface, clazz)) - .isTrue(); - } - @Test void getServers() throws NoSuchMethodException { Method m = ClassWithOperationBindingProcessor.class.getDeclaredMethod("methodWithAnnotation", String.class); @@ -276,20 +258,4 @@ private static class ClassWithMultipleOperationBindingProcessors { @TestAbstractOperationBindingProcessor.TestOperationBinding() private void methodWithAnnotation(String payload) {} } - - private static class ClassImplementingInterface implements ClassInterface { - - @AsyncListener( - operation = - @AsyncOperation( - channelName = "${test.property.test-channel}", - description = "${test.property.description}")) - @TestOperationBindingProcessor.TestOperationBinding() - @Override - public void methodFromInterface(String payload) {} - } - - public static interface ClassInterface { - void methodFromInterface(T payload); - } } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java index 99d827f69..b658e7e5a 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java @@ -22,6 +22,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; @@ -243,10 +245,11 @@ void scan_componentHasAsyncMethodAnnotation() { assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } - @Test - void scan_componentHasOnlyDeclaredMethods() { + @ParameterizedTest + @ValueSource(classes = {ClassImplementingInterface.class, ClassImplementingInterfaceWithAnnotation.class}) + void scan_componentHasOnlyDeclaredMethods(Class clazz) { // Given a class with a method, which is declared in a generic interface - setClassToScan(ClassImplementingInterface.class); + setClassToScan(clazz); // When scan is called Map actualChannels = channelScanner.scan(); @@ -351,7 +354,7 @@ private void methodWithAnnotation(SimpleFoo payload) {} private void methodWithoutAnnotation() {} } - private static class ClassImplementingInterface implements AsyncAnnotationScannerUtilTest.ClassInterface { + private static class ClassImplementingInterface implements ClassInterface { @AsyncListener( operation = @@ -362,6 +365,26 @@ private static class ClassImplementingInterface implements AsyncAnnotationScanne public void methodFromInterface(String payload) {} } + interface ClassInterface { + void methodFromInterface(T payload); + } + + private static class ClassImplementingInterfaceWithAnnotation + implements AsyncPublisherAnnotationScannerIntegrationTest.ClassInterfaceWithAnnotation { + + @Override + public void methodFromInterface(String payload) {} + } + + interface ClassInterfaceWithAnnotation { + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + void methodFromInterface(T payload); + } + @Data @NoArgsConstructor @Schema(description = "SimpleFoo Message Description") diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java index 72b21fc29..32bdd00a9 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScannerIntegrationTest.java @@ -17,6 +17,8 @@ import lombok.NoArgsConstructor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; @@ -205,10 +207,11 @@ void scan_componentHasPublisherMethodWithAsyncMessageAnnotation() { assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } - @Test - void scan_componentHasOnlyDeclaredMethods() { + @ParameterizedTest + @ValueSource(classes = {ClassImplementingInterface.class, ClassImplementingInterfaceWithAnnotation.class}) + void scan_componentHasOnlyDeclaredMethods(Class clazz) { // Given a class with a method, which is declared in a generic interface - setClassToScan(ClassImplementingInterface.class); + setClassToScan(clazz); // When scan is called Map actualChannels = channelScanner.scan(); @@ -276,7 +279,7 @@ private static class ClassWithMultipleListenerAnnotations { private void methodWithMultipleAnnotation(SimpleFoo payload) {} } - private static class ClassImplementingInterface implements AsyncAnnotationScannerUtilTest.ClassInterface { + private static class ClassImplementingInterface implements ClassInterface { @AsyncPublisher( operation = @@ -287,6 +290,25 @@ private static class ClassImplementingInterface implements AsyncAnnotationScanne public void methodFromInterface(String payload) {} } + interface ClassInterface { + void methodFromInterface(T payload); + } + + private static class ClassImplementingInterfaceWithAnnotation implements ClassInterfaceWithAnnotation { + + @Override + public void methodFromInterface(String payload) {} + } + + interface ClassInterfaceWithAnnotation { + @AsyncPublisher( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + void methodFromInterface(T payload); + } + @Data @NoArgsConstructor private static class SimpleFoo { From 94865085292266e5fd6c22c68090bcf2afc93a28 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Fri, 17 Nov 2023 17:30:57 +0100 Subject: [PATCH 3/4] feat: add Inherited annotation --- .../annotation/AsyncGenericOperationBinding.java | 2 ++ .../operationdata/annotation/AsyncListener.java | 2 ++ .../operationdata/annotation/AsyncListeners.java | 4 +++- .../operationdata/annotation/AsyncMessage.java | 2 ++ .../operationdata/annotation/AsyncOperation.java | 10 +++++++--- .../operationdata/annotation/AsyncPublisher.java | 2 ++ .../operationdata/annotation/AsyncPublishers.java | 4 +++- .../TestAbstractOperationBindingProcessor.java | 2 ++ .../processor/TestMessageBindingProcessor.java | 2 ++ .../processor/TestOperationBindingProcessor.java | 2 ++ .../TestMethodLevelListenerScannerIntegrationTest.java | 2 ++ .../annotation/AmqpAsyncOperationBinding.java | 2 ++ .../annotation/KafkaAsyncOperationBinding.java | 6 ++++-- .../annotation/SnsAsyncOperationBinding.java | 2 ++ .../annotation/SqsAsyncOperationBinding.java | 2 ++ 15 files changed, 39 insertions(+), 7 deletions(-) diff --git a/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/AsyncGenericOperationBinding.java b/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/AsyncGenericOperationBinding.java index 2dbd0dc8d..f1b455cde 100644 --- a/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/AsyncGenericOperationBinding.java +++ b/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/AsyncGenericOperationBinding.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -17,6 +18,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @AsyncOperationBinding +@Inherited public @interface AsyncGenericOperationBinding { /** * The name of the binding diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListener.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListener.java index 60d56e3ad..9d4707937 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListener.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListener.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.types.OperationData; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,6 +34,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Repeatable(AsyncListeners.class) +@Inherited public @interface AsyncListener { /** * Mapped to {@link OperationData} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListeners.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListeners.java index 1e8fad821..80701b072 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListeners.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListeners.java @@ -2,12 +2,14 @@ package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) -@interface AsyncListeners { +@Inherited +public @interface AsyncListeners { AsyncListener[] value(); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java index b60196bc3..e795bb8ff 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -15,6 +16,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) +@Inherited public @interface AsyncMessage { /** * Mapped to {@link Message#getDescription()} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncOperation.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncOperation.java index c88634535..fb65aa2ae 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncOperation.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncOperation.java @@ -5,6 +5,7 @@ import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaderSchema; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -12,8 +13,9 @@ /** * Annotation is mapped to {@link OperationData} */ -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target({}) +@Inherited public @interface AsyncOperation { /** * Mapped to {@link OperationData#getChannelName()} @@ -50,7 +52,8 @@ @Retention(RetentionPolicy.CLASS) @Target({}) - @interface Headers { + @Inherited + public @interface Headers { /** * Mapped to {@link AsyncHeaders#getSchemaName()} */ @@ -60,7 +63,8 @@ @Retention(RetentionPolicy.CLASS) @Target({}) - @interface Header { + @Inherited + public @interface Header { /** * Mapped to {@link AsyncHeaderSchema#getHeaderName()} */ diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisher.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisher.java index 841c73a1b..bc1387724 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisher.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisher.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.types.OperationData; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,6 +33,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Repeatable(AsyncPublishers.class) +@Inherited public @interface AsyncPublisher { /** * Mapped to {@link OperationData} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublishers.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublishers.java index ff1fa5143..dadf36960 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublishers.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublishers.java @@ -2,12 +2,14 @@ package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) -@interface AsyncPublishers { +@Inherited +public @interface AsyncPublishers { AsyncPublisher[] value(); } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java index 30d13dd5a..0ae2ec9f6 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java @@ -8,6 +8,7 @@ import org.springframework.core.annotation.Order; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -27,6 +28,7 @@ protected ProcessedOperationBinding mapToOperationBinding(TestOperationBinding b @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @AsyncOperationBinding + @Inherited public @interface TestOperationBinding { TestMessageBindingProcessor.TestMessageBinding operationBinding() default @TestMessageBindingProcessor.TestMessageBinding(); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java index ff93beff6..ae599d372 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java @@ -8,6 +8,7 @@ import org.springframework.core.annotation.Order; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -37,5 +38,6 @@ private ProcessedMessageBinding mapToMessageBinding( @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) + @Inherited public @interface TestMessageBinding {} } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java index a1c5b2dd2..87e977c45 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java @@ -8,6 +8,7 @@ import org.springframework.core.annotation.Order; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -30,6 +31,7 @@ public Optional process(Method method) { @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) + @Inherited public @interface TestOperationBinding { TestMessageBindingProcessor.TestMessageBinding operationBinding() default @TestMessageBindingProcessor.TestMessageBinding(); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/TestMethodLevelListenerScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/TestMethodLevelListenerScannerIntegrationTest.java index c2409a379..995928975 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/TestMethodLevelListenerScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/TestMethodLevelListenerScannerIntegrationTest.java @@ -22,6 +22,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -119,5 +120,6 @@ private static class SimpleFoo { @Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) + @Inherited public @interface TestChannelListener {} } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java index d324f5eb5..cac4cb243 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -15,6 +16,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @AsyncOperationBinding +@Inherited public @interface AmqpAsyncOperationBinding { String type() default "amqp"; diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/KafkaAsyncOperationBinding.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/KafkaAsyncOperationBinding.java index 507d14fca..c40faea92 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/KafkaAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/KafkaAsyncOperationBinding.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -15,6 +16,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @AsyncOperationBinding +@Inherited public @interface KafkaAsyncOperationBinding { String type() default "kafka"; @@ -29,7 +31,7 @@ @Retention(RetentionPolicy.CLASS) @Target({}) - @interface KafkaAsyncMessageBinding { + public @interface KafkaAsyncMessageBinding { KafkaAsyncKey key() default @KafkaAsyncKey(type = KafkaAsyncKey.KafkaKeyTypes.UNDEFINED_KEY); @@ -38,7 +40,7 @@ @Retention(RetentionPolicy.CLASS) @Target({}) - @interface KafkaAsyncKey { + public @interface KafkaAsyncKey { KafkaKeyTypes type() default KafkaKeyTypes.STRING_KEY; diff --git a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java index a9b584c40..351a01022 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -15,6 +16,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @AsyncOperationBinding +@Inherited public @interface SnsAsyncOperationBinding { String type() default "sns"; diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java index ab137c177..fbc0cf9cc 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -15,6 +16,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @AsyncOperationBinding +@Inherited public @interface SqsAsyncOperationBinding { String type() default "sqs"; From 562da4c8bbbfccc2b4f7d377d157ab5331376dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Fri, 17 Nov 2023 18:38:28 +0100 Subject: [PATCH 4/4] feat(core): support multiple annotations on the same method Co-authored-by: Timon Back --- .../AsyncListenerAnnotationScanner.java | 22 +++++++++++++++--- .../AsyncPublisherAnnotationScanner.java | 23 +++++++++++++++---- ...tenerAnnotationScannerIntegrationTest.java | 5 ++-- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java index 8e0f8599b..b25b7765e 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScanner.java @@ -16,12 +16,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotationCollectors; +import org.springframework.core.annotation.MergedAnnotationPredicates; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.RepeatableContainers; import org.springframework.util.StringValueResolver; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -67,8 +74,8 @@ private Stream getAnnotatedMethods(Class type) { return Arrays.stream(type.getDeclaredMethods()) .filter(method -> !method.isBridge()) - .filter(method -> method.isAnnotationPresent(annotationClass) - || method.isAnnotationPresent(annotationClassRepeatable)); + .filter(method -> AnnotationUtils.findAnnotation(method, annotationClass) != null + || AnnotationUtils.findAnnotation(method, annotationClassRepeatable) != null); } private Stream toOperationData(Method method) { @@ -81,7 +88,16 @@ private Stream toOperationData(Method method) { Message message = AsyncAnnotationScannerUtil.processMessageFromAnnotation(method); Class annotationClass = AsyncListener.class; - return Arrays.stream(method.getAnnotationsByType(annotationClass)) + Set annotations = MergedAnnotations.from( + method, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, + RepeatableContainers.standardRepeatables()) + .stream(annotationClass) + .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + + return annotations.stream() .map(annotation -> toConsumerData(method, operationBindings, messageBindings, message, annotation)); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java index b1bf8af90..ebcdb6402 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncPublisherAnnotationScanner.java @@ -16,12 +16,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotationCollectors; +import org.springframework.core.annotation.MergedAnnotationPredicates; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.RepeatableContainers; import org.springframework.util.StringValueResolver; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; @Slf4j @@ -67,9 +74,8 @@ private Stream getAnnotatedMethods(Class type) { return Arrays.stream(type.getDeclaredMethods()) .filter(method -> !method.isBridge()) - .filter(method ->org.springframework.core.annotation.AnnotationsUtils.getAnnotation(method, annotationClass) != null); -// .filter(method -> method.isAnnotationPresent(annotationClass) -// || method.isAnnotationPresent(annotationClassRepeatable)); + .filter(method -> AnnotationUtils.findAnnotation(method, annotationClass) != null + || AnnotationUtils.findAnnotation(method, annotationClassRepeatable) != null); } private Stream toOperationData(Method method) { @@ -82,7 +88,16 @@ private Stream toOperationData(Method method) { Message message = AsyncAnnotationScannerUtil.processMessageFromAnnotation(method); Class annotationClass = AsyncPublisher.class; - return Arrays.stream(method.getAnnotationsByType(annotationClass)) + Set annotations = MergedAnnotations.from( + method, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, + RepeatableContainers.standardRepeatables()) + .stream(annotationClass) + .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) + .map(MergedAnnotation::withNonMergedAttributes) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + + return annotations.stream() .map(annotation -> toConsumerData(method, operationBindings, messageBindings, message, annotation)); } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java index b658e7e5a..b39d59036 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncListenerAnnotationScannerIntegrationTest.java @@ -246,7 +246,7 @@ void scan_componentHasAsyncMethodAnnotation() { } @ParameterizedTest - @ValueSource(classes = {ClassImplementingInterface.class, ClassImplementingInterfaceWithAnnotation.class}) + @ValueSource(classes = {ClassImplementingInterfaceWithAnnotation.class}) void scan_componentHasOnlyDeclaredMethods(Class clazz) { // Given a class with a method, which is declared in a generic interface setClassToScan(clazz); @@ -369,8 +369,7 @@ interface ClassInterface { void methodFromInterface(T payload); } - private static class ClassImplementingInterfaceWithAnnotation - implements AsyncPublisherAnnotationScannerIntegrationTest.ClassInterfaceWithAnnotation { + private static class ClassImplementingInterfaceWithAnnotation implements ClassInterfaceWithAnnotation { @Override public void methodFromInterface(String payload) {}