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..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,16 +6,28 @@ 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 { + + // maintainer note: replaced by #getChannelId(T, BindingContext) default String getChannelId(T annotation) { return ReferenceUtil.toValidId(getChannelName(annotation)); } + // 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); Map buildOperationBinding(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 73b3853da..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,15 +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, methods, SpringAnnotationMessagesService.MessageType.CHANNEL)); + classAnnotation, bindingContext, methods, SpringAnnotationMessagesService.MessageType.CHANNEL)); - return mapClassToChannel(classAnnotation, messages); + return mapClassToChannel(classAnnotation, bindingContext, messages); } - private Stream mapClassToChannel(ClassAnnotation classAnnotation, Map messages) { - return Stream.of(springAnnotationChannelService.buildChannel(classAnnotation, messages)); + private Stream mapClassToChannel( + 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 2cf7cacf8..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,6 +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, 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 bc82378cf..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, 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); + 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 71b2f5e5b..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, 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); + 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 a40550aac..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; @@ -25,11 +26,14 @@ public class SpringAnnotationOperationService springAnnotationMessageService; public Operation buildOperation( - MethodAnnotation annotation, PayloadSchemaObject payloadType, SchemaObject headerSchema) { + MethodAnnotation annotation, + 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); + 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 d90feb9c7..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,16 +24,18 @@ public class SpringAnnotationOperationsService bindingFactory; private final SpringAnnotationMessagesService springAnnotationMessagesService; - public Operation buildOperation(ClassAnnotation classAnnotation, Set methods) { + public Operation buildOperation( + ClassAnnotation classAnnotation, BindingContext bindingContext, Set methods) { var messages = springAnnotationMessagesService.buildMessages( - classAnnotation, methods, SpringAnnotationMessagesService.MessageType.OPERATION); - return buildOperation(classAnnotation, messages); + classAnnotation, bindingContext, methods, SpringAnnotationMessagesService.MessageType.OPERATION); + return buildOperation(classAnnotation, bindingContext, messages); } - private Operation buildOperation(ClassAnnotation classAnnotation, Map messages) { + private Operation buildOperation( + ClassAnnotation classAnnotation, BindingContext bindingContext, 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, 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 0a2ed6701..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); + 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, 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 ef3ac9834..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,15 +43,17 @@ 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); + String channelId = bindingFactory.getChannelId(annotation, bindingContext); 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, bindingContext, 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/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/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..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; @@ -40,7 +41,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 +60,11 @@ void scan_componentHasTestListenerMethods() throws NoSuchMethodException { .thenReturn(messageObject); // when - Operation operations = - springAnnotationOperationService.buildOperation(annotation, payloadSchemaName, headerSchema); + Operation operations = springAnnotationOperationService.buildOperation( + annotation, + BindingContext.ofAnnotatedClass(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-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-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..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; @@ -21,6 +22,11 @@ public String getChannelName(JmsListener annotation) { return JmsListenerUtil.getChannelName(annotation, stringValueResolver); } + @Override + public String getChannelName(JmsListener annotation, BindingContext bindingContext) { + return JmsListenerUtil.getChannelName(annotation, stringValueResolver); + } + @Override public Map buildChannelBinding(JmsListener annotation) { return JmsListenerUtil.buildChannelBinding(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 73d13b28e..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,20 +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 lombok.RequiredArgsConstructor; import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.util.StringValueResolver; import java.util.Map; @RequiredArgsConstructor public class KafkaBindingFactory implements BindingFactory { - private final StringValueResolver stringValueResolver; + private final KafkaBeanRefHelper kafkaBeanRefHelper; @Override public String getChannelName(KafkaListener annotation) { - return KafkaListenerUtil.getChannelName(annotation, stringValueResolver); + 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 @@ -29,7 +36,8 @@ 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 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..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; @@ -46,8 +47,17 @@ public class SpringwolfKafkaScannerConfiguration { name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, havingValue = "true", matchIfMissing = true) - public KafkaBindingFactory kafkaBindingFactory(StringValueResolver stringValueResolver) { - return new KafkaBindingFactory(stringValueResolver); + 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-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/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..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())).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