From c0dc484371bb07a628fd3df9e10e4e0d05c0ba2b Mon Sep 17 00:00:00 2001 From: ruskaof <199-41@mail.ru> Date: Wed, 20 Nov 2024 23:10:43 +0300 Subject: [PATCH 1/2] feat: support for beanRef in KafkaListener annotation --- .../scanners/bindings/BindingFactory.java | 6 +- ...ngAnnotationClassLevelChannelsScanner.java | 9 +- ...gAnnotationMethodLevelChannelsScanner.java | 3 +- .../SpringAnnotationChannelService.java | 4 +- .../SpringAnnotationMessagesService.java | 4 +- .../SpringAnnotationOperationService.java | 7 +- .../SpringAnnotationOperationsService.java | 11 +- ...AnnotationClassLevelOperationsScanner.java | 4 +- ...nnotationMethodLevelOperationsScanner.java | 6 +- ...ssLevelChannelsScannerIntegrationTest.java | 2 +- ...notationClassLevelChannelsScannerTest.java | 4 +- ...odLevelChannelsScannerIntegrationTest.java | 2 +- ...otationMethodLevelChannelsScannerTest.java | 2 +- .../SpringAnnotationOperationServiceTest.java | 6 +- ...tationClassLevelOperationsScannerTest.java | 8 +- ...ationMethodLevelOperationsScannerTest.java | 6 +- .../scanners/bindings/AmqpBindingFactory.java | 4 +- .../scanners/bindings/JmsBindingFactory.java | 2 +- .../bindings/KafkaBindingFactory.java | 105 +++++++++++++++++- .../SpringwolfKafkaScannerConfiguration.java | 5 +- .../scanners/bindings/SqsBindingFactory.java | 2 +- .../StompBindingMessageMappingFactory.java | 2 +- .../bindings/StompBindingSendToFactory.java | 2 +- .../StompBindingSendToUserFactory.java | 2 +- .../annotations/SendToCustomizerTest.java | 4 +- .../annotations/SendToUserCustomizerTest.java | 2 +- 26 files changed, 160 insertions(+), 54 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java index 5f89b7e8a..69b81e6be 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java @@ -10,11 +10,11 @@ import java.util.Map; public interface BindingFactory { - default String getChannelId(T annotation) { - return ReferenceUtil.toValidId(getChannelName(annotation)); + default String getChannelId(T annotation, Class component) { + return ReferenceUtil.toValidId(getChannelName(annotation, component)); } - String getChannelName(T annotation); + String getChannelName(T annotation, Class component); Map buildChannelBinding(T annotation); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java index 73b3853da..fdcd77792 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java @@ -45,12 +45,13 @@ private Stream mapClassToChannel( Set methods = annotatedMethods.stream().map(MethodAndAnnotation::method).collect(Collectors.toSet()); Map messages = new HashMap<>(springAnnotationMessagesService.buildMessages( - classAnnotation, methods, SpringAnnotationMessagesService.MessageType.CHANNEL)); + classAnnotation, component, methods, SpringAnnotationMessagesService.MessageType.CHANNEL)); - return mapClassToChannel(classAnnotation, messages); + return mapClassToChannel(classAnnotation, component, messages); } - private Stream mapClassToChannel(ClassAnnotation classAnnotation, Map messages) { - return Stream.of(springAnnotationChannelService.buildChannel(classAnnotation, messages)); + private Stream mapClassToChannel( + ClassAnnotation classAnnotation, Class component, Map messages) { + return Stream.of(springAnnotationChannelService.buildChannel(classAnnotation, component, messages)); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java index 2cf7cacf8..3d0c3d8c5 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java @@ -48,6 +48,7 @@ private ChannelObject mapMethodToChannel(MethodAndAnnotation m MessageObject message = springAnnotationMessageService.buildMessage(annotation, payloadSchema, headerSchema); Map messages = Map.of(message.getMessageId(), MessageReference.toComponentMessage(message)); - return springAnnotationChannelService.buildChannel(annotation, messages); + return springAnnotationChannelService.buildChannel( + annotation, method.method().getDeclaringClass(), messages); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java index bc82378cf..98a88c924 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java @@ -18,10 +18,10 @@ public class SpringAnnotationChannelService bindingFactory; - public ChannelObject buildChannel(Annotation annotation, Map messages) { + public ChannelObject buildChannel(Annotation annotation, Class component, Map messages) { Map channelBinding = bindingFactory.buildChannelBinding(annotation); Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; - String channelName = bindingFactory.getChannelName(annotation); + String channelName = bindingFactory.getChannelName(annotation, component); return ChannelObject.builder() .channelId(ReferenceUtil.toValidId(channelName)) .address(channelName) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java index 71b2f5e5b..ee09a244c 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java @@ -43,13 +43,13 @@ public enum MessageType { } public Map buildMessages( - ClassAnnotation classAnnotation, Set methods, MessageType messageType) { + ClassAnnotation classAnnotation, Class component, Set methods, MessageType messageType) { Set messages = methods.stream() .map(method -> buildMessage(classAnnotation, method)) .collect(toSet()); if (messageType == MessageType.OPERATION) { - String channelId = bindingFactory.getChannelName(classAnnotation); + String channelId = bindingFactory.getChannelName(classAnnotation, component); return toOperationsMessagesMap(channelId, messages); } return toMessagesMap(messages); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java index a40550aac..9b7957ecc 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java @@ -25,11 +25,14 @@ public class SpringAnnotationOperationService springAnnotationMessageService; public Operation buildOperation( - MethodAnnotation annotation, PayloadSchemaObject payloadType, SchemaObject headerSchema) { + MethodAnnotation annotation, + Class component, + PayloadSchemaObject payloadType, + SchemaObject headerSchema) { MessageObject message = springAnnotationMessageService.buildMessage(annotation, payloadType, headerSchema); Map operationBinding = bindingFactory.buildOperationBinding(annotation); Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - String channelId = bindingFactory.getChannelId(annotation); + String channelId = bindingFactory.getChannelId(annotation, component); return Operation.builder() .action(OperationAction.RECEIVE) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java index d90feb9c7..1de3f04a2 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java @@ -23,16 +23,17 @@ public class SpringAnnotationOperationsService bindingFactory; private final SpringAnnotationMessagesService springAnnotationMessagesService; - public Operation buildOperation(ClassAnnotation classAnnotation, Set methods) { + public Operation buildOperation(ClassAnnotation classAnnotation, Class component, Set methods) { var messages = springAnnotationMessagesService.buildMessages( - classAnnotation, methods, SpringAnnotationMessagesService.MessageType.OPERATION); - return buildOperation(classAnnotation, messages); + classAnnotation, component, methods, SpringAnnotationMessagesService.MessageType.OPERATION); + return buildOperation(classAnnotation, component, messages); } - private Operation buildOperation(ClassAnnotation classAnnotation, Map messages) { + private Operation buildOperation( + ClassAnnotation classAnnotation, Class component, Map messages) { Map operationBinding = bindingFactory.buildOperationBinding(classAnnotation); Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - String channelName = bindingFactory.getChannelName(classAnnotation); + String channelName = bindingFactory.getChannelName(classAnnotation, component); String channelId = ReferenceUtil.toValidId(channelName); return Operation.builder() diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java index 0a2ed6701..6bffbd197 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java @@ -43,13 +43,13 @@ private Stream> mapClassToOperation( Class component, Set> annotatedMethods) { ClassAnnotation classAnnotation = AnnotationUtil.findFirstAnnotationOrThrow(classAnnotationClass, component); - String channelId = bindingFactory.getChannelId(classAnnotation); + String channelId = bindingFactory.getChannelId(classAnnotation, component); String operationId = StringUtils.joinWith("_", channelId, OperationAction.RECEIVE.type, component.getSimpleName()); Set methods = annotatedMethods.stream().map(MethodAndAnnotation::method).collect(Collectors.toSet()); - Operation operation = springAnnotationOperationsService.buildOperation(classAnnotation, methods); + Operation operation = springAnnotationOperationsService.buildOperation(classAnnotation, component, methods); annotatedMethods.forEach( method -> customizers.forEach(customizer -> customizer.customize(operation, method.method()))); return Stream.of(Map.entry(operationId, operation)); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java index ef3ac9834..493ce10a0 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java @@ -43,14 +43,16 @@ public Stream> scan(Class clazz) { private Map.Entry mapMethodToOperation(MethodAndAnnotation method) { MethodAnnotation annotation = AnnotationUtil.findFirstAnnotationOrThrow(methodAnnotationClass, method.method()); - String channelId = bindingFactory.getChannelId(annotation); + String channelId = + bindingFactory.getChannelId(annotation, method.method().getDeclaringClass()); String operationId = StringUtils.joinWith( "_", channelId, OperationAction.RECEIVE.type, method.method().getName()); PayloadSchemaObject payloadSchema = payloadMethodParameterService.extractSchema(method.method()); SchemaObject headerSchema = headerClassExtractor.extractHeader(method.method(), payloadSchema); - Operation operation = springAnnotationOperationService.buildOperation(annotation, payloadSchema, headerSchema); + Operation operation = springAnnotationOperationService.buildOperation( + annotation, method.method().getDeclaringClass(), payloadSchema, headerSchema); customizers.forEach(customizer -> customizer.customize(operation, method.method())); return Map.entry(operationId, operation); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java index 6bbbd7955..882e6d11f 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java @@ -291,7 +291,7 @@ static class TestBindingFactory implements BindingFactory { Map.of(CHANNEL_ID, new TestBindingFactory.TestOperationBinding()); @Override - public String getChannelName(TestClassListener annotation) { + public String getChannelName(TestClassListener annotation, Class component) { return CHANNEL; } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java index a15035018..f9e01369d 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java @@ -40,7 +40,7 @@ void scan() { MessageReference message = MessageReference.toComponentMessage("messageId"); Map messages = Map.of("messageId", message); - when(springAnnotationChannelService.buildChannel(any(), any())) + when(springAnnotationChannelService.buildChannel(any(), any(), any())) .thenReturn(ChannelObject.builder() .channelId(TestBindingFactory.CHANNEL_ID) .messages(messages) @@ -58,7 +58,7 @@ void scan() { int methodsInClass = 2; verify(springAnnotationMessagesService) - .buildMessages(any(), argThat(list -> list.size() == methodsInClass), any()); + .buildMessages(any(), any(), argThat(list -> list.size() == methodsInClass), any()); } @TestClassListener diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java index 4f64b080d..ab0d0490d 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java @@ -311,7 +311,7 @@ static class TestBindingFactory implements BindingFactory { Map.of(CHANNEL_ID, new TestBindingFactory.TestOperationBinding()); @Override - public String getChannelName(TestChannelListener annotation) { + public String getChannelName(TestChannelListener annotation, Class component) { return CHANNEL; } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java index 17c8f3971..f83f3000b 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java @@ -49,7 +49,7 @@ void scan() { .build(); when(springAnnotationMessageService.buildMessage(any(), any(), any())).thenReturn(message); - when(springAnnotationChannelService.buildChannel(any(), any())).thenReturn(expectedChannelItem); + when(springAnnotationChannelService.buildChannel(any(), any(), any())).thenReturn(expectedChannelItem); // when List channels = scanner.scan(ClassWithTestListenerAnnotation.class); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java index cc0b9fb48..4343c5075 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java @@ -40,7 +40,7 @@ class SpringAnnotationOperationServiceTest { @BeforeEach void setUp() { // when - when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelId(any(), any())).thenReturn(CHANNEL_ID); doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any()); } @@ -59,8 +59,8 @@ void scan_componentHasTestListenerMethods() throws NoSuchMethodException { .thenReturn(messageObject); // when - Operation operations = - springAnnotationOperationService.buildOperation(annotation, payloadSchemaName, headerSchema); + Operation operations = springAnnotationOperationService.buildOperation( + annotation, ClassWithTestListenerAnnotation.class, payloadSchemaName, headerSchema); // then Operation expectedOperation = Operation.builder() diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java index c0626c184..ff43824a9 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java @@ -42,14 +42,15 @@ class SpringAnnotationClassLevelOperationsScannerTest { @BeforeEach void setUp() { - when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_NAME_ID); + when(bindingFactory.getChannelId(any(), any())).thenReturn(CHANNEL_NAME_ID); } @Test void scan() { // given Operation operation = Operation.builder().build(); - when(springAnnotationOperationsService.buildOperation(any(), anySet())).thenReturn(operation); + when(springAnnotationOperationsService.buildOperation(any(), any(), anySet())) + .thenReturn(operation); // when List> operations = @@ -64,7 +65,8 @@ void scan() { void operationCustomizerIsCalled() { // given Operation operation = Operation.builder().build(); - when(springAnnotationOperationsService.buildOperation(any(), anySet())).thenReturn(operation); + when(springAnnotationOperationsService.buildOperation(any(), any(), anySet())) + .thenReturn(operation); // when scanner.scan(ClassWithTestListenerAnnotation.class).toList(); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java index 6a1090d08..7ea3e9213 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java @@ -41,14 +41,14 @@ class SpringAnnotationMethodLevelOperationsScannerTest { @BeforeEach void setUp() { - when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelId(any(), any())).thenReturn(CHANNEL_ID); } @Test void scan_componentHasTestListenerMethods() { // given Operation operation = Operation.builder().build(); - when(springAnnotationOperationService.buildOperation(any(), any(), any())) + when(springAnnotationOperationService.buildOperation(any(), any(), any(), any())) .thenReturn(operation); // when @@ -63,7 +63,7 @@ void scan_componentHasTestListenerMethods() { @Test void operationCustomizerIsCalled() { // given Operation operation = Operation.builder().build(); - when(springAnnotationOperationService.buildOperation(any(), any(), any())) + when(springAnnotationOperationService.buildOperation(any(), any(), any(), any())) .thenReturn(operation); // when diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java index 80c53ebbd..2fe9205c1 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java @@ -29,12 +29,12 @@ public AmqpBindingFactory( } @Override - public String getChannelName(RabbitListener annotation) { + public String getChannelName(RabbitListener annotation, Class component) { return RabbitListenerUtil.getChannelName(annotation, stringValueResolver); } @Override - public String getChannelId(RabbitListener annotation) { + public String getChannelId(RabbitListener annotation, Class component) { return RabbitListenerUtil.getChannelId(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java index 8713ebeba..8fb0750c7 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java @@ -17,7 +17,7 @@ public class JmsBindingFactory implements BindingFactory { private final StringValueResolver stringValueResolver; @Override - public String getChannelName(JmsListener annotation) { + public String getChannelName(JmsListener annotation, Class component) { return JmsListenerUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java index 73d13b28e..59ee79699 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java @@ -7,19 +7,40 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.plugins.kafka.asyncapi.scanners.common.KafkaListenerUtil; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.util.StringValueResolver; +import java.util.HashMap; import java.util.Map; @RequiredArgsConstructor -public class KafkaBindingFactory implements BindingFactory { - private final StringValueResolver stringValueResolver; +public class KafkaBindingFactory implements BindingFactory, ApplicationContextAware { + private final ListenerScope listenerScope = new ListenerScope(); + private ListenerStringValueResolver stringValueResolver; + private ApplicationContext applicationContext; + private BeanExpressionContext expressionContext; + private BeanExpressionResolver resolver = new StandardBeanExpressionResolver(); @Override - public String getChannelName(KafkaListener annotation) { - return KafkaListenerUtil.getChannelName(annotation, stringValueResolver); + public String getChannelName(KafkaListener annotation, Class component) { + listenerScope.addListener(annotation.beanRef(), applicationContext.getBean(component)); + String result = KafkaListenerUtil.getChannelName(annotation, stringValueResolver); + listenerScope.removeListener(annotation.beanRef()); + return result; } @Override @@ -36,4 +57,80 @@ public Map buildOperationBinding(KafkaListener annotat public Map buildMessageBinding(KafkaListener annotation, SchemaObject headerSchema) { return KafkaListenerUtil.buildMessageBinding(headerSchema); } + + @Override + public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + if (applicationContext instanceof ConfigurableApplicationContext cac) { + setBeanFactory(cac.getBeanFactory()); + } else { + setBeanFactory(applicationContext); + } + + stringValueResolver = new ListenerStringValueResolver(expressionContext, resolver); + } + + private void setBeanFactory(BeanFactory beanFactory) { + if (beanFactory instanceof ConfigurableListableBeanFactory clbf) { + BeanExpressionResolver beanExpressionResolver = clbf.getBeanExpressionResolver(); + if (beanExpressionResolver != null) { + this.resolver = beanExpressionResolver; + } + this.expressionContext = new BeanExpressionContext(clbf, this.listenerScope); + } + } + + private record ListenerStringValueResolver( + BeanExpressionContext beanExpressionContext, BeanExpressionResolver beanExpressionResolver) + implements StringValueResolver { + @Override + @Nullable + public String resolveStringValue(@Nonnull String strVal) { + if (beanExpressionContext == null) { + return strVal; + } + + Object resolved = beanExpressionResolver.evaluate(strVal, beanExpressionContext); + return resolved == null ? null : resolved.toString(); + } + } + + private static class ListenerScope implements Scope { + + private final Map listeners = new HashMap<>(); + + ListenerScope() {} + + public void addListener(String key, Object bean) { + this.listeners.put(key, bean); + } + + public void removeListener(String key) { + this.listeners.remove(key); + } + + @Override + @Nonnull + public Object get(@Nonnull String name, @Nonnull ObjectFactory objectFactory) { + return this.listeners.get(name); + } + + @Override + public Object remove(@Nonnull String name) { + return null; + } + + @Override + public void registerDestructionCallback(@Nonnull String name, @Nonnull Runnable callback) {} + + @Override + public Object resolveContextualObject(@Nonnull String key) { + return this.listeners.get(key); + } + + @Override + public String getConversationId() { + return null; + } + } } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java index 0e1bf5867..f0a093eba 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java @@ -29,7 +29,6 @@ import org.springframework.core.annotation.Order; import org.springframework.kafka.annotation.KafkaHandler; import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.util.StringValueResolver; import java.util.List; @@ -46,8 +45,8 @@ public class SpringwolfKafkaScannerConfiguration { name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, havingValue = "true", matchIfMissing = true) - public KafkaBindingFactory kafkaBindingFactory(StringValueResolver stringValueResolver) { - return new KafkaBindingFactory(stringValueResolver); + public KafkaBindingFactory kafkaBindingFactory() { + return new KafkaBindingFactory(); } @Bean diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java index 047d191a2..86d25d9f8 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java @@ -17,7 +17,7 @@ public class SqsBindingFactory implements BindingFactory { private final StringValueResolver stringValueResolver; @Override - public String getChannelName(SqsListener annotation) { + public String getChannelName(SqsListener annotation, Class component) { return SqsListenerUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java index 0c788a088..3dc4a12a7 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java @@ -20,7 +20,7 @@ public class StompBindingMessageMappingFactory implements BindingFactory component) { return properties.getEndpoint().getApp() + MessageMappingUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java index 11728d2b5..787a77633 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java @@ -20,7 +20,7 @@ public class StompBindingSendToFactory implements BindingFactory { private final StringValueResolver stringValueResolver; @Override - public String getChannelName(SendTo annotation) { + public String getChannelName(SendTo annotation, Class component) { return properties.getEndpoint().getApp() + SendToUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java index d62c097fb..9023203e9 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java @@ -21,7 +21,7 @@ public class StompBindingSendToUserFactory implements BindingFactory private final StringValueResolver stringValueResolver; @Override - public String getChannelName(SendToUser annotation) { + public String getChannelName(SendToUser annotation, Class component) { return properties.getEndpoint().getUser() + SendToUserUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java index 3056ca92b..7e69bb4c8 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java @@ -31,8 +31,8 @@ class SendToCustomizerTest { void customize() throws NoSuchMethodException { // given Operation operation = new Operation(); - when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); - when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelId(any(), any())).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelName(any(), any())).thenReturn(CHANNEL_ID); when(payloadService.extractSchema(any())).thenReturn(new PayloadSchemaObject(MESSAGE_ID, MESSAGE_ID, null)); // when diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java index 47b23afcb..d9e51192b 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java @@ -32,7 +32,7 @@ void customize() throws NoSuchMethodException { // given Operation operation = new Operation(); when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); - when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelName(any(), component)).thenReturn(CHANNEL_ID); when(payloadService.extractSchema(any())).thenReturn(new PayloadSchemaObject(MESSAGE_ID, MESSAGE_ID, null)); // when From 3667cef912d61a389c0be07a6ed5c31b9a35cf9d Mon Sep 17 00:00:00 2001 From: ruskaof <199-41@mail.ru> Date: Mon, 2 Dec 2024 18:29:43 +0300 Subject: [PATCH 2/2] fix: create new methods in BindingFactory instead of changing the existing ones, move bean ref logic to KafkaBeanRefHelper, implement BindingContext for passing Method and Class context to factories --- .../scanners/bindings/BindingFactory.java | 18 ++- .../bindings/common/BindingContext.java | 31 +++++ ...ngAnnotationClassLevelChannelsScanner.java | 10 +- ...gAnnotationMethodLevelChannelsScanner.java | 6 +- .../SpringAnnotationChannelService.java | 6 +- .../SpringAnnotationMessagesService.java | 8 +- .../SpringAnnotationOperationService.java | 5 +- .../SpringAnnotationOperationsService.java | 12 +- ...AnnotationClassLevelOperationsScanner.java | 7 +- ...nnotationMethodLevelOperationsScanner.java | 7 +- ...ssLevelChannelsScannerIntegrationTest.java | 2 +- ...odLevelChannelsScannerIntegrationTest.java | 2 +- .../SpringAnnotationOperationServiceTest.java | 6 +- .../ExampleBeanRefKafkaListener.java | 45 +++++++ .../scanners/bindings/AmqpBindingFactory.java | 4 +- .../scanners/bindings/JmsBindingFactory.java | 8 +- .../scanners/bindings/KafkaBeanRefHelper.java | 106 ++++++++++++++++ .../bindings/KafkaBindingFactory.java | 117 +++--------------- .../SpringwolfKafkaScannerConfiguration.java | 15 ++- .../scanners/bindings/SqsBindingFactory.java | 2 +- .../StompBindingMessageMappingFactory.java | 2 +- .../bindings/StompBindingSendToFactory.java | 2 +- .../StompBindingSendToUserFactory.java | 2 +- .../annotations/SendToCustomizer.java | 3 +- .../annotations/SendToUserCustomizer.java | 3 +- .../annotations/SendToUserCustomizerTest.java | 4 +- 26 files changed, 289 insertions(+), 144 deletions(-) create mode 100644 springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/common/BindingContext.java create mode 100644 springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/ExampleBeanRefKafkaListener.java create mode 100644 springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBeanRefHelper.java diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java index 69b81e6be..3a86cbbfc 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java @@ -6,15 +6,27 @@ import io.github.springwolf.asyncapi.v3.bindings.OperationBinding; import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import java.util.Map; public interface BindingFactory { - default String getChannelId(T annotation, Class component) { - return ReferenceUtil.toValidId(getChannelName(annotation, component)); + + // maintainer note: replaced by #getChannelId(T, BindingContext) + default String getChannelId(T annotation) { + return ReferenceUtil.toValidId(getChannelName(annotation)); } - String getChannelName(T annotation, Class component); + // maintainer note: replaced by #getChannelName(T, BindingContext) + String getChannelName(T annotation); + + default String getChannelId(T annotation, BindingContext bindingContext) { + return getChannelId(annotation); + } + + default String getChannelName(T annotation, BindingContext bindingContext) { + return getChannelName(annotation); + } Map buildChannelBinding(T annotation); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/common/BindingContext.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/common/BindingContext.java new file mode 100644 index 000000000..bcacb9478 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/common/BindingContext.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.bindings.common; + +import java.lang.reflect.Method; + +public record BindingContext(Class annotatedClass, Method annotatedMethod) { + public BindingContext { + if (annotatedClass == null && annotatedMethod == null) { + throw new IllegalArgumentException("Either annotatedClass or annotatedMethod must be non-null"); + } + } + + public Class getClassContext() { + if (annotatedClass != null) { + return annotatedClass; + } + if (annotatedMethod != null) { + return annotatedMethod.getDeclaringClass(); + } + + throw new IllegalStateException("Either annotatedClass or annotatedMethod must be non-null"); + } + + public static BindingContext ofAnnotatedMethod(Method annotatedMethod) { + return new BindingContext(null, annotatedMethod); + } + + public static BindingContext ofAnnotatedClass(Class annotatedClass) { + return new BindingContext(annotatedClass, null); + } +} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java index fdcd77792..bfdd75bc9 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java @@ -3,6 +3,7 @@ import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import io.github.springwolf.asyncapi.v3.model.channel.message.Message; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.channels.ChannelsInClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationUtil; @@ -42,16 +43,17 @@ public List scan(Class clazz) { private Stream mapClassToChannel( Class component, Set> annotatedMethods) { ClassAnnotation classAnnotation = AnnotationUtil.findFirstAnnotationOrThrow(classAnnotationClass, component); + BindingContext bindingContext = BindingContext.ofAnnotatedClass(component); Set methods = annotatedMethods.stream().map(MethodAndAnnotation::method).collect(Collectors.toSet()); Map messages = new HashMap<>(springAnnotationMessagesService.buildMessages( - classAnnotation, component, methods, SpringAnnotationMessagesService.MessageType.CHANNEL)); + classAnnotation, bindingContext, methods, SpringAnnotationMessagesService.MessageType.CHANNEL)); - return mapClassToChannel(classAnnotation, component, messages); + return mapClassToChannel(classAnnotation, bindingContext, messages); } private Stream mapClassToChannel( - ClassAnnotation classAnnotation, Class component, Map messages) { - return Stream.of(springAnnotationChannelService.buildChannel(classAnnotation, component, messages)); + ClassAnnotation classAnnotation, BindingContext bindingContext, Map messages) { + return Stream.of(springAnnotationChannelService.buildChannel(classAnnotation, bindingContext, messages)); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java index 3d0c3d8c5..507374adb 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java @@ -6,6 +6,7 @@ import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.channels.ChannelsInClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationUtil; @@ -48,7 +49,8 @@ private ChannelObject mapMethodToChannel(MethodAndAnnotation m MessageObject message = springAnnotationMessageService.buildMessage(annotation, payloadSchema, headerSchema); Map messages = Map.of(message.getMessageId(), MessageReference.toComponentMessage(message)); - return springAnnotationChannelService.buildChannel( - annotation, method.method().getDeclaringClass(), messages); + BindingContext bindingContext = BindingContext.ofAnnotatedMethod(method.method()); + + return springAnnotationChannelService.buildChannel(annotation, bindingContext, messages); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java index 98a88c924..dd399a20d 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/channel/SpringAnnotationChannelService.java @@ -6,6 +6,7 @@ import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import io.github.springwolf.asyncapi.v3.model.channel.message.Message; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,10 +19,11 @@ public class SpringAnnotationChannelService bindingFactory; - public ChannelObject buildChannel(Annotation annotation, Class component, Map messages) { + public ChannelObject buildChannel( + Annotation annotation, BindingContext bindingContext, Map messages) { Map channelBinding = bindingFactory.buildChannelBinding(annotation); Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; - String channelName = bindingFactory.getChannelName(annotation, component); + String channelName = bindingFactory.getChannelName(annotation, bindingContext); return ChannelObject.builder() .channelId(ReferenceUtil.toValidId(channelName)) .address(channelName) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java index ee09a244c..a13b3528a 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/message/SpringAnnotationMessagesService.java @@ -10,6 +10,7 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.common.headers.HeaderClassExtractor; import io.github.springwolf.core.asyncapi.scanners.common.headers.HeaderSchemaObjectMerger; @@ -43,13 +44,16 @@ public enum MessageType { } public Map buildMessages( - ClassAnnotation classAnnotation, Class component, Set methods, MessageType messageType) { + ClassAnnotation classAnnotation, + BindingContext bindingContext, + Set methods, + MessageType messageType) { Set messages = methods.stream() .map(method -> buildMessage(classAnnotation, method)) .collect(toSet()); if (messageType == MessageType.OPERATION) { - String channelId = bindingFactory.getChannelName(classAnnotation, component); + String channelId = bindingFactory.getChannelName(classAnnotation, bindingContext); return toOperationsMessagesMap(channelId, messages); } return toMessagesMap(messages); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java index 9b7957ecc..e217a6386 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationService.java @@ -9,6 +9,7 @@ import io.github.springwolf.asyncapi.v3.model.operation.OperationAction; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.message.SpringAnnotationMessageService; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadSchemaObject; import lombok.RequiredArgsConstructor; @@ -26,13 +27,13 @@ public class SpringAnnotationOperationService component, + BindingContext bindingContext, PayloadSchemaObject payloadType, SchemaObject headerSchema) { MessageObject message = springAnnotationMessageService.buildMessage(annotation, payloadType, headerSchema); Map operationBinding = bindingFactory.buildOperationBinding(annotation); Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - String channelId = bindingFactory.getChannelId(annotation, component); + String channelId = bindingFactory.getChannelId(annotation, bindingContext); return Operation.builder() .action(OperationAction.RECEIVE) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java index 1de3f04a2..36e6ff11e 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationsService.java @@ -8,6 +8,7 @@ import io.github.springwolf.asyncapi.v3.model.operation.Operation; import io.github.springwolf.asyncapi.v3.model.operation.OperationAction; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.message.SpringAnnotationMessagesService; import lombok.RequiredArgsConstructor; @@ -23,17 +24,18 @@ public class SpringAnnotationOperationsService bindingFactory; private final SpringAnnotationMessagesService springAnnotationMessagesService; - public Operation buildOperation(ClassAnnotation classAnnotation, Class component, Set methods) { + public Operation buildOperation( + ClassAnnotation classAnnotation, BindingContext bindingContext, Set methods) { var messages = springAnnotationMessagesService.buildMessages( - classAnnotation, component, methods, SpringAnnotationMessagesService.MessageType.OPERATION); - return buildOperation(classAnnotation, component, messages); + classAnnotation, bindingContext, methods, SpringAnnotationMessagesService.MessageType.OPERATION); + return buildOperation(classAnnotation, bindingContext, messages); } private Operation buildOperation( - ClassAnnotation classAnnotation, Class component, Map messages) { + ClassAnnotation classAnnotation, BindingContext bindingContext, Map messages) { Map operationBinding = bindingFactory.buildOperationBinding(classAnnotation); Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - String channelName = bindingFactory.getChannelName(classAnnotation, component); + String channelName = bindingFactory.getChannelName(classAnnotation, bindingContext); String channelId = ReferenceUtil.toValidId(channelName); return Operation.builder() diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java index 6bffbd197..d7e50c848 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java @@ -4,6 +4,7 @@ import io.github.springwolf.asyncapi.v3.model.operation.Operation; import io.github.springwolf.asyncapi.v3.model.operation.OperationAction; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationUtil; import io.github.springwolf.core.asyncapi.scanners.common.annotation.MethodAndAnnotation; @@ -42,14 +43,16 @@ public Stream> scan(Class clazz) { private Stream> mapClassToOperation( Class component, Set> annotatedMethods) { ClassAnnotation classAnnotation = AnnotationUtil.findFirstAnnotationOrThrow(classAnnotationClass, component); + BindingContext bindingContext = BindingContext.ofAnnotatedClass(component); - String channelId = bindingFactory.getChannelId(classAnnotation, component); + String channelId = bindingFactory.getChannelId(classAnnotation, bindingContext); String operationId = StringUtils.joinWith("_", channelId, OperationAction.RECEIVE.type, component.getSimpleName()); Set methods = annotatedMethods.stream().map(MethodAndAnnotation::method).collect(Collectors.toSet()); - Operation operation = springAnnotationOperationsService.buildOperation(classAnnotation, component, methods); + Operation operation = + springAnnotationOperationsService.buildOperation(classAnnotation, bindingContext, methods); annotatedMethods.forEach( method -> customizers.forEach(customizer -> customizer.customize(operation, method.method()))); return Stream.of(Map.entry(operationId, operation)); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java index 493ce10a0..dc8207862 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java @@ -5,6 +5,7 @@ import io.github.springwolf.asyncapi.v3.model.operation.OperationAction; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationUtil; import io.github.springwolf.core.asyncapi.scanners.common.annotation.MethodAndAnnotation; @@ -42,9 +43,9 @@ public Stream> scan(Class clazz) { private Map.Entry mapMethodToOperation(MethodAndAnnotation method) { MethodAnnotation annotation = AnnotationUtil.findFirstAnnotationOrThrow(methodAnnotationClass, method.method()); + BindingContext bindingContext = BindingContext.ofAnnotatedMethod(method.method()); - String channelId = - bindingFactory.getChannelId(annotation, method.method().getDeclaringClass()); + String channelId = bindingFactory.getChannelId(annotation, bindingContext); String operationId = StringUtils.joinWith( "_", channelId, OperationAction.RECEIVE.type, method.method().getName()); @@ -52,7 +53,7 @@ private Map.Entry mapMethodToOperation(MethodAndAnnotation customizer.customize(operation, method.method())); return Map.entry(operationId, operation); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java index 882e6d11f..6bbbd7955 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java @@ -291,7 +291,7 @@ static class TestBindingFactory implements BindingFactory { Map.of(CHANNEL_ID, new TestBindingFactory.TestOperationBinding()); @Override - public String getChannelName(TestClassListener annotation, Class component) { + public String getChannelName(TestClassListener annotation) { return CHANNEL; } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java index ab0d0490d..4f64b080d 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java @@ -311,7 +311,7 @@ static class TestBindingFactory implements BindingFactory { Map.of(CHANNEL_ID, new TestBindingFactory.TestOperationBinding()); @Override - public String getChannelName(TestChannelListener annotation, Class component) { + public String getChannelName(TestChannelListener annotation) { return CHANNEL; } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java index 4343c5075..e56ddc4d1 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/operation/SpringAnnotationOperationServiceTest.java @@ -10,6 +10,7 @@ import io.github.springwolf.asyncapi.v3.model.operation.OperationAction; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.message.SpringAnnotationMessageService; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadSchemaObject; import org.junit.jupiter.api.BeforeEach; @@ -60,7 +61,10 @@ void scan_componentHasTestListenerMethods() throws NoSuchMethodException { // when Operation operations = springAnnotationOperationService.buildOperation( - annotation, ClassWithTestListenerAnnotation.class, payloadSchemaName, headerSchema); + annotation, + BindingContext.ofAnnotatedClass(ClassWithTestListenerAnnotation.class), + payloadSchemaName, + headerSchema); // then Operation expectedOperation = Operation.builder() diff --git a/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/ExampleBeanRefKafkaListener.java b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/ExampleBeanRefKafkaListener.java new file mode 100644 index 000000000..434c71f9a --- /dev/null +++ b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/ExampleBeanRefKafkaListener.java @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.examples.kafka.consumers; + +import io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto; +import io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto; +import io.github.springwolf.examples.kafka.producers.AnotherProducer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@Slf4j +public class ExampleBeanRefKafkaListener { + + @SuppressWarnings("unused") + public final String TOPIC_NAME = "example-topic-from-bean-ref"; + + private final AnotherProducer anotherProducer; + + @KafkaListener(topics = "#{myListener.TOPIC_NAME}", beanRef = "myListener") + public void receiveExamplePayload( + @Header(KafkaHeaders.RECEIVED_KEY) String key, + @Header(KafkaHeaders.OFFSET) Integer offset, + @Payload ExamplePayloadDto payload) { + log.info("Received new message in example-topic: {}", payload.toString()); + + AnotherPayloadDto example = new AnotherPayloadDto(); + example.setExample(payload); + example.setFoo("foo"); + + anotherProducer.sendMessage(example); + } + + @KafkaListener(topicPattern = "another-topic", groupId = "example-group-id", batch = "true") + public void receiveAnotherPayloadBatched(List payloads) { + log.info("Received new message in another-topic: {}", payloads.toString()); + } +} diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java index 2fe9205c1..80c53ebbd 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java @@ -29,12 +29,12 @@ public AmqpBindingFactory( } @Override - public String getChannelName(RabbitListener annotation, Class component) { + public String getChannelName(RabbitListener annotation) { return RabbitListenerUtil.getChannelName(annotation, stringValueResolver); } @Override - public String getChannelId(RabbitListener annotation, Class component) { + public String getChannelId(RabbitListener annotation) { return RabbitListenerUtil.getChannelId(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java index 8fb0750c7..7fefdc132 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/asyncapi/scanners/bindings/JmsBindingFactory.java @@ -6,6 +6,7 @@ import io.github.springwolf.asyncapi.v3.bindings.OperationBinding; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import lombok.RequiredArgsConstructor; import org.springframework.jms.annotation.JmsListener; import org.springframework.util.StringValueResolver; @@ -17,7 +18,12 @@ public class JmsBindingFactory implements BindingFactory { private final StringValueResolver stringValueResolver; @Override - public String getChannelName(JmsListener annotation, Class component) { + public String getChannelName(JmsListener annotation) { + return JmsListenerUtil.getChannelName(annotation, stringValueResolver); + } + + @Override + public String getChannelName(JmsListener annotation, BindingContext bindingContext) { return JmsListenerUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBeanRefHelper.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBeanRefHelper.java new file mode 100644 index 000000000..da7430697 --- /dev/null +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBeanRefHelper.java @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.kafka.asyncapi.scanners.bindings; + +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.expression.StandardBeanExpressionResolver; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.util.StringValueResolver; + +import java.util.Collections; +import java.util.Map; + +@RequiredArgsConstructor +public class KafkaBeanRefHelper implements ApplicationContextAware { + private final StringValueResolver defaultStringValueResolver; + private BeanFactory beanFactory; + + public StringValueResolver getStringValueResolver( + KafkaListener annotation, @Nullable BindingContext bindingContext) { + if (bindingContext == null) { + return defaultStringValueResolver; + } + + ListenerScope listenerScope = + new ListenerScope(annotation.beanRef(), beanFactory.getBean(bindingContext.getClassContext())); + BeanExpressionContext beanExpressionContext = null; + BeanExpressionResolver resolver = new StandardBeanExpressionResolver(); + if (beanFactory instanceof ConfigurableListableBeanFactory clbf) { + BeanExpressionResolver beanExpressionResolver = clbf.getBeanExpressionResolver(); + if (beanExpressionResolver != null) { + resolver = beanExpressionResolver; + } + beanExpressionContext = new BeanExpressionContext(clbf, listenerScope); + } + return new ListenerStringValueResolver(beanExpressionContext, resolver); + } + + private static class ListenerScope implements Scope { + + private final Map listeners; + + public ListenerScope(String listenerBeanRef, Object listener) { + listeners = Collections.singletonMap(listenerBeanRef, listener); + } + + @Override + @Nonnull + public Object get(@Nonnull String name, @Nonnull ObjectFactory objectFactory) { + return this.listeners.get(name); + } + + @Override + public Object remove(@Nonnull String name) { + return null; + } + + @Override + public void registerDestructionCallback(@Nonnull String name, @Nonnull Runnable callback) {} + + @Override + public Object resolveContextualObject(@Nonnull String key) { + return this.listeners.get(key); + } + + @Override + public String getConversationId() { + return null; + } + } + + @Override + public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException { + if (applicationContext instanceof ConfigurableApplicationContext cac) { + this.beanFactory = cac.getBeanFactory(); + } else { + this.beanFactory = applicationContext; + } + } + + private record ListenerStringValueResolver( + BeanExpressionContext beanExpressionContext, BeanExpressionResolver beanExpressionResolver) + implements StringValueResolver { + @Override + @Nullable + public String resolveStringValue(@Nonnull String strVal) { + if (beanExpressionContext == null) { + return strVal; + } + + Object resolved = beanExpressionResolver.evaluate(strVal, beanExpressionContext); + return resolved == null ? null : resolved.toString(); + } + } +} diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java index 59ee79699..d00173434 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/bindings/KafkaBindingFactory.java @@ -6,41 +6,27 @@ import io.github.springwolf.asyncapi.v3.bindings.OperationBinding; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.plugins.kafka.asyncapi.scanners.common.KafkaListenerUtil; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ObjectFactory; -import org.springframework.beans.factory.config.BeanExpressionContext; -import org.springframework.beans.factory.config.BeanExpressionResolver; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.Scope; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.util.StringValueResolver; -import java.util.HashMap; import java.util.Map; @RequiredArgsConstructor -public class KafkaBindingFactory implements BindingFactory, ApplicationContextAware { - private final ListenerScope listenerScope = new ListenerScope(); - private ListenerStringValueResolver stringValueResolver; - private ApplicationContext applicationContext; - private BeanExpressionContext expressionContext; - private BeanExpressionResolver resolver = new StandardBeanExpressionResolver(); +public class KafkaBindingFactory implements BindingFactory { + private final KafkaBeanRefHelper kafkaBeanRefHelper; @Override - public String getChannelName(KafkaListener annotation, Class component) { - listenerScope.addListener(annotation.beanRef(), applicationContext.getBean(component)); - String result = KafkaListenerUtil.getChannelName(annotation, stringValueResolver); - listenerScope.removeListener(annotation.beanRef()); - return result; + public String getChannelName(KafkaListener annotation) { + return KafkaListenerUtil.getChannelName( + annotation, kafkaBeanRefHelper.getStringValueResolver(annotation, null)); + } + + @Override + public String getChannelName(KafkaListener annotation, BindingContext bindingContext) { + return KafkaListenerUtil.getChannelName( + annotation, kafkaBeanRefHelper.getStringValueResolver(annotation, bindingContext)); } @Override @@ -50,87 +36,12 @@ public Map buildChannelBinding(KafkaListener annotation) @Override public Map buildOperationBinding(KafkaListener annotation) { - return KafkaListenerUtil.buildOperationBinding(annotation, stringValueResolver); + return KafkaListenerUtil.buildOperationBinding( + annotation, kafkaBeanRefHelper.getStringValueResolver(annotation, null)); } @Override public Map buildMessageBinding(KafkaListener annotation, SchemaObject headerSchema) { return KafkaListenerUtil.buildMessageBinding(headerSchema); } - - @Override - public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - if (applicationContext instanceof ConfigurableApplicationContext cac) { - setBeanFactory(cac.getBeanFactory()); - } else { - setBeanFactory(applicationContext); - } - - stringValueResolver = new ListenerStringValueResolver(expressionContext, resolver); - } - - private void setBeanFactory(BeanFactory beanFactory) { - if (beanFactory instanceof ConfigurableListableBeanFactory clbf) { - BeanExpressionResolver beanExpressionResolver = clbf.getBeanExpressionResolver(); - if (beanExpressionResolver != null) { - this.resolver = beanExpressionResolver; - } - this.expressionContext = new BeanExpressionContext(clbf, this.listenerScope); - } - } - - private record ListenerStringValueResolver( - BeanExpressionContext beanExpressionContext, BeanExpressionResolver beanExpressionResolver) - implements StringValueResolver { - @Override - @Nullable - public String resolveStringValue(@Nonnull String strVal) { - if (beanExpressionContext == null) { - return strVal; - } - - Object resolved = beanExpressionResolver.evaluate(strVal, beanExpressionContext); - return resolved == null ? null : resolved.toString(); - } - } - - private static class ListenerScope implements Scope { - - private final Map listeners = new HashMap<>(); - - ListenerScope() {} - - public void addListener(String key, Object bean) { - this.listeners.put(key, bean); - } - - public void removeListener(String key) { - this.listeners.remove(key); - } - - @Override - @Nonnull - public Object get(@Nonnull String name, @Nonnull ObjectFactory objectFactory) { - return this.listeners.get(name); - } - - @Override - public Object remove(@Nonnull String name) { - return null; - } - - @Override - public void registerDestructionCallback(@Nonnull String name, @Nonnull Runnable callback) {} - - @Override - public Object resolveContextualObject(@Nonnull String key) { - return this.listeners.get(key); - } - - @Override - public String getConversationId() { - return null; - } - } } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java index f0a093eba..df4ccd45b 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java @@ -20,6 +20,7 @@ import io.github.springwolf.core.asyncapi.scanners.operations.annotations.OperationCustomizer; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner; +import io.github.springwolf.plugins.kafka.asyncapi.scanners.bindings.KafkaBeanRefHelper; import io.github.springwolf.plugins.kafka.asyncapi.scanners.bindings.KafkaBindingFactory; import io.github.springwolf.plugins.kafka.asyncapi.scanners.common.header.AsyncHeadersForKafkaBuilder; import lombok.val; @@ -29,6 +30,7 @@ import org.springframework.core.annotation.Order; import org.springframework.kafka.annotation.KafkaHandler; import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.util.StringValueResolver; import java.util.List; @@ -45,8 +47,17 @@ public class SpringwolfKafkaScannerConfiguration { name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, havingValue = "true", matchIfMissing = true) - public KafkaBindingFactory kafkaBindingFactory() { - return new KafkaBindingFactory(); + public KafkaBeanRefHelper kafkaBeanRefHelper(StringValueResolver stringValueResolver) { + return new KafkaBeanRefHelper(stringValueResolver); + } + + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + public KafkaBindingFactory kafkaBindingFactory(KafkaBeanRefHelper kafkaBeanRefHelper) { + return new KafkaBindingFactory(kafkaBeanRefHelper); } @Bean diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java index 86d25d9f8..047d191a2 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/asyncapi/scanners/bindings/SqsBindingFactory.java @@ -17,7 +17,7 @@ public class SqsBindingFactory implements BindingFactory { private final StringValueResolver stringValueResolver; @Override - public String getChannelName(SqsListener annotation, Class component) { + public String getChannelName(SqsListener annotation) { return SqsListenerUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java index 3dc4a12a7..0c788a088 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingMessageMappingFactory.java @@ -20,7 +20,7 @@ public class StompBindingMessageMappingFactory implements BindingFactory component) { + public String getChannelName(MessageMapping annotation) { return properties.getEndpoint().getApp() + MessageMappingUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java index 787a77633..11728d2b5 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToFactory.java @@ -20,7 +20,7 @@ public class StompBindingSendToFactory implements BindingFactory { private final StringValueResolver stringValueResolver; @Override - public String getChannelName(SendTo annotation, Class component) { + public String getChannelName(SendTo annotation) { return properties.getEndpoint().getApp() + SendToUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java index 9023203e9..d62c097fb 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/bindings/StompBindingSendToUserFactory.java @@ -21,7 +21,7 @@ public class StompBindingSendToUserFactory implements BindingFactory private final StringValueResolver stringValueResolver; @Override - public String getChannelName(SendToUser annotation, Class component) { + public String getChannelName(SendToUser annotation) { return properties.getEndpoint().getUser() + SendToUserUtil.getChannelName(annotation, stringValueResolver); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java index 4e4836f24..0bf3fc251 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java @@ -5,6 +5,7 @@ import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.asyncapi.v3.model.operation.Operation; import io.github.springwolf.asyncapi.v3.model.operation.OperationReply; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationUtil; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodReturnService; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.OperationCustomizer; @@ -24,7 +25,7 @@ public class SendToCustomizer implements OperationCustomizer { public void customize(Operation operation, Method method) { SendTo annotation = AnnotationUtil.findFirstAnnotation(SendTo.class, method); if (annotation != null) { - String channelId = bindingFactory.getChannelId(annotation); + String channelId = bindingFactory.getChannelId(annotation, BindingContext.ofAnnotatedMethod(method)); String payloadName = payloadService.extractSchema(method).name(); operation.setReply(OperationReply.builder() diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java index 086899c39..23b225d11 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java @@ -5,6 +5,7 @@ import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.asyncapi.v3.model.operation.Operation; import io.github.springwolf.asyncapi.v3.model.operation.OperationReply; +import io.github.springwolf.core.asyncapi.scanners.bindings.common.BindingContext; import io.github.springwolf.core.asyncapi.scanners.common.annotation.AnnotationUtil; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodReturnService; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.OperationCustomizer; @@ -24,7 +25,7 @@ public class SendToUserCustomizer implements OperationCustomizer { public void customize(Operation operation, Method method) { SendToUser annotation = AnnotationUtil.findFirstAnnotation(SendToUser.class, method); if (annotation != null) { - String channelId = bindingFactory.getChannelId(annotation); + String channelId = bindingFactory.getChannelId(annotation, BindingContext.ofAnnotatedMethod(method)); String payloadName = payloadService.extractSchema(method).name(); operation.setReply(OperationReply.builder() diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java index d9e51192b..abce4d70a 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java @@ -31,8 +31,8 @@ class SendToUserCustomizerTest { void customize() throws NoSuchMethodException { // given Operation operation = new Operation(); - when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); - when(bindingFactory.getChannelName(any(), component)).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelId(any(), any())).thenReturn(CHANNEL_ID); + when(bindingFactory.getChannelName(any(), any())).thenReturn(CHANNEL_ID); when(payloadService.extractSchema(any())).thenReturn(new PayloadSchemaObject(MESSAGE_ID, MESSAGE_ID, null)); // when