diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java index 02c1b702f..434627c1d 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java @@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; @@ -58,7 +59,7 @@ protected Stream> getAnnotatedMethods(Class type) { Class annotationClass = this.asyncAnnotationProvider.getAnnotation(); log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName()); - return Arrays.stream(type.getDeclaredMethods()) + return Arrays.stream(ReflectionUtils.getAllDeclaredMethods(type)) .filter(method -> !method.isBridge()) .filter(method -> AnnotationScannerUtil.findAnnotation(annotationClass, method) != null) .peek(method -> log.debug("Mapping method \"{}\" to channels", method.getName())) diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java index 3bbffdced..4119b3b97 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java @@ -297,6 +297,43 @@ void scan_componentHasAsyncMethodAnnotation() { .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); } + @Test + void scan_componentHasAsyncMethodAnnotationInAbstractClass() { + // Given a class with methods annotated with AsyncListener, where only the channel-name is set + setClassToScan(ClassExtendsFromAbstractWithListenerAnnotation.class); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId("simpleFoo") + .name("SimpleFooPayLoad") + .title("Message Title") + .description("Message description") + .payload(payload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) + .bindings(EMPTY_MAP) + .build(); + + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("abstract-test-channel")) + .description("test abstract channel operation description") + .title("abstract-test-channel_send") + .bindings(EMPTY_MAP) + .messages(List.of(MessageReference.toChannelMessage("abstract-test-channel", message))) + .build(); + + assertThat(actualOperations) + .containsExactly(Map.entry("abstract-test-channel_send_methodWithAnnotation", expectedOperation)); + } + private static class ClassWithoutListenerAnnotation { private void methodWithoutAnnotation() {} @@ -370,6 +407,25 @@ private void methodWithAnnotation(SimpleFoo payload) {} private void methodWithoutAnnotation() {} } + private static class ClassExtendsFromAbstractWithListenerAnnotation extends AbstractClassWithListenerAnnotation {} + + private abstract static class AbstractClassWithListenerAnnotation { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "abstract-test-channel", + description = "test abstract channel operation description", + message = + @AsyncMessage( + description = "Message description", + messageId = "simpleFooAbstract", + name = "SimpleFooPayLoad", + contentType = "application/json", + title = "Message Title"))) + protected void methodWithAnnotation(SimpleFoo payload) {} + } + @Nested class ImplementingInterface { @ParameterizedTest