diff --git a/springwolf-plugins/springwolf-amqp-plugin/build.gradle b/springwolf-plugins/springwolf-amqp-plugin/build.gradle index 3f2c81897..29c0b4669 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/build.gradle +++ b/springwolf-plugins/springwolf-amqp-plugin/build.gradle @@ -33,6 +33,7 @@ dependencies { testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" + testImplementation("org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}") testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" testImplementation "org.springframework.boot:spring-boot-test" diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java index 26d492c6c..64c464081 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java @@ -34,13 +34,12 @@ public class RabbitListenerUtil { private static final String DEFAULT_EXCHANGE_TYPE = ExchangeTypes.DIRECT; public static String getChannelName(RabbitListener annotation, StringValueResolver resolver) { - Stream annotationQueueNames = Arrays.stream(annotation.queues()); Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> Stream.concat( Stream.of(binding.key()), // if routing key is configured, prefer it Stream.of(binding.value().name()))); - return Stream.concat(annotationQueueNames, annotationBindingChannelNames) + return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) .map(resolver::resolveStringValue) .filter(Objects::nonNull) .peek(queue -> log.debug("Resolved channel name: {}", queue)) @@ -51,11 +50,10 @@ public static String getChannelName(RabbitListener annotation, StringValueResolv } public static String getQueueName(RabbitListener annotation, StringValueResolver resolver) { - Stream annotationQueueNames = Arrays.stream(annotation.queues()); Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> Stream.of(binding.value().name())); - return Stream.concat(annotationQueueNames, annotationBindingChannelNames) + return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) .map(resolver::resolveStringValue) .filter(Objects::nonNull) .peek(queue -> log.debug("Resolved queue name: {}", queue)) @@ -65,6 +63,23 @@ public static String getQueueName(RabbitListener annotation, StringValueResolver "No queue name was found in @RabbitListener annotation (neither in queues nor bindings property)")); } + /** + * + * @param rabbitListenerAnnotation a RabbitListener annotation + * @return A stream of ALL queue names as defined in the following 'locations': + *
    + *
  • {@link RabbitListener#queues()}
  • + *
  • {@link RabbitListener#queuesToDeclare()}.name
  • + *
+ * Note: queues, queuesToDeclare (and bindings) are mutually exclusive + * @see RabbitListener.queuesToDeclare + * */ + private static Stream streamQueueNames(RabbitListener rabbitListenerAnnotation) { + return Stream.concat( + Arrays.stream(rabbitListenerAnnotation.queues()), + Arrays.stream(rabbitListenerAnnotation.queuesToDeclare()).map(Queue::name)); + } + public static Map buildChannelBinding( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { AMQPChannelBinding.AMQPChannelBindingBuilder channelBinding = AMQPChannelBinding.builder(); diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelRabbitListenerScannerIntegrationTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelRabbitListenerScannerIntegrationTest.java index ebfa2d918..1081164d7 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelRabbitListenerScannerIntegrationTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelRabbitListenerScannerIntegrationTest.java @@ -12,13 +12,14 @@ import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; -import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.schemas.DefaultSchemasService; import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator; import lombok.Data; 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.amqp.core.Binding; import org.springframework.amqp.core.ExchangeTypes; import org.springframework.amqp.rabbit.annotation.Exchange; @@ -61,9 +62,6 @@ class MethodLevelRabbitListenerScannerIntegrationTest { @MockBean private ComponentClassScanner componentsScanner; - @MockBean - private AsyncApiDocket asyncApiDocket; - private static final String QUEUE = "test-queue"; private static final Map defaultOperationBinding = Map.of("amqp", AMQPOperationBinding.builder().cc(List.of(QUEUE)).build()); @@ -103,10 +101,15 @@ void scan_componentHasNoRabbitListenerMethods() { assertThat(channels).isEmpty(); } - @Test - void scan_componentHasRabbitListenerMethods_hardCodedTopic() { + @ParameterizedTest + @ValueSource( + classes = { + ClassWithRabbitListenerAnnotationHardCodedTopic.class, + ClassWithRabbitListenerAnnotationUsingQueuesToDeclare.class + }) + void scan_componentHasRabbitListenerMethods_hardCodedTopic(Class classWithRabbitListenerAnnotation) { // Given a class with methods annotated with RabbitListener, whose queues attribute is hard coded - setClassToScan(ClassWithRabbitListenerAnnotationHardCodedTopic.class); + setClassToScan(classWithRabbitListenerAnnotation); // When scan is called Map actualChannelItems = rabbitListenerScanner.scan(); @@ -128,7 +131,7 @@ void scan_componentHasRabbitListenerMethods_hardCodedTopic() { Operation operation = Operation.builder() .description("Auto-generated description") - .operationId("test-queue_publish_methodWithAnnotation") + .operationId(QUEUE + "_publish_methodWithAnnotation") .bindings(defaultOperationBinding) .message(message) .build(); @@ -177,7 +180,7 @@ void scan_componentHasRabbitListenerMethods_embeddedValueTopic() { Operation operation = Operation.builder() .description("Auto-generated description") - .operationId("test-queue_publish_methodWithAnnotation1") + .operationId(QUEUE + "_publish_methodWithAnnotation1") .bindings(defaultOperationBinding) .message(message) .build(); @@ -223,7 +226,7 @@ void scan_componentHasRabbitListenerMethods_bindingsAnnotation() { Operation operation = Operation.builder() .description("Auto-generated description") - .operationId("key_publish_methodWithAnnotation1") + .operationId("key_publish_methodWithAnnotation") .bindings(Map.of( "amqp", AMQPOperationBinding.builder() @@ -329,7 +332,7 @@ void scan_componentHasRabbitListenerMethods_multipleParamsWithPayloadAnnotation( Operation operation = Operation.builder() .description("Auto-generated description") - .operationId("test-queue_publish_methodWithAnnotation") + .operationId(QUEUE + "_publish_methodWithAnnotation") .bindings(defaultOperationBinding) .message(message) .build(); @@ -362,9 +365,18 @@ private static class ClassWithRabbitListenerAnnotationUsingBindings { @QueueBinding( exchange = @Exchange(name = "name", type = "topic"), key = "key", - value = @Queue(name = "test-queue")) + value = @Queue(name = QUEUE)) }) - private void methodWithAnnotation1(SimpleFoo payload) {} + private void methodWithAnnotation(SimpleFoo payload) {} + } + + /** + * Note: bindings, queues, and queuesToDeclare are mutually exclusive + * @see RabbitListener.queuesToDeclare + */ + private static class ClassWithRabbitListenerAnnotationUsingQueuesToDeclare { + @RabbitListener(queuesToDeclare = @Queue(name = QUEUE)) + private void methodWithAnnotation(SimpleFoo payload) {} } public static class ClassWithRabbitListenerAnnotationsBindingBean {