Skip to content

Commit

Permalink
feat(stomp): add OperationCustomizer
Browse files Browse the repository at this point in the history
allows to add reply for SendTo and SendToUser
  • Loading branch information
timonback committed Jun 9, 2024
1 parent be43abb commit 2e2a623
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.operations.annotations;

import io.github.springwolf.asyncapi.v3.model.operation.Operation;

import java.lang.reflect.Method;

public interface OperationCustomizer {
void customize(Operation operation, Method method);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ public class SpringAnnotationMethodLevelOperationsScanner<MethodAnnotation exten
extends MethodLevelAnnotationScanner<MethodAnnotation> implements SpringAnnotationOperationsScannerDelegator {

private final Class<MethodAnnotation> methodAnnotationClass;
private final List<OperationCustomizer> customizers;
private final PayloadMethodParameterService payloadService;

public SpringAnnotationMethodLevelOperationsScanner(
Class<MethodAnnotation> methodAnnotationClass,
BindingFactory<MethodAnnotation> bindingFactory,
AsyncHeadersBuilder asyncHeadersBuilder,
List<OperationCustomizer> customizers,
PayloadMethodParameterService payloadService,
ComponentsService componentsService) {
super(bindingFactory, asyncHeadersBuilder, componentsService);
this.customizers = customizers;
this.methodAnnotationClass = methodAnnotationClass;
this.payloadService = payloadService;
}
Expand Down Expand Up @@ -69,15 +72,13 @@ private Map.Entry<String, Operation> mapMethodToOperation(Method method) {
NamedSchemaObject payloadSchema = payloadService.extractSchema(method);

Operation operation = buildOperation(annotation, payloadSchema);
customizers.forEach(customizer -> customizer.customize(operation, method));

return Map.entry(operationId, operation);
}

private Operation buildOperation(MethodAnnotation annotation, NamedSchemaObject payloadType) {
MessageObject message = buildMessage(annotation, payloadType);
return buildOperation(annotation, message);
}

private Operation buildOperation(MethodAnnotation annotation, MessageObject message) {
Map<String, OperationBinding> operationBinding = bindingFactory.buildOperationBinding(annotation);
Map<String, OperationBinding> opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null;
String channelId = ReferenceUtil.toValidId(bindingFactory.getChannelName(annotation));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadAsyncOperationService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodParameterService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodReturnService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.internal.PayloadClassExtractor;
import io.github.springwolf.core.asyncapi.scanners.common.payload.internal.PayloadService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.internal.TypeToClassConverter;
Expand Down Expand Up @@ -199,7 +198,7 @@ public PayloadMethodParameterService payloadMethodParameterService(

@Bean
@ConditionalOnMissingBean
public PayloadMethodService payloadMethodReturnService(PayloadService payloadService) {
public PayloadMethodReturnService payloadMethodReturnService(PayloadService payloadService) {
return new PayloadMethodReturnService(payloadService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,17 @@
{
"$ref": "#/channels/_app_queue_sendto-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto"
}
]
],
"reply": {
"channel": {
"$ref": "#/channels/_app_topic_sendto-response-queue"
},
"messages": [
{
"$ref": "#/channels/_app_topic_sendto-response-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto"
}
]
}
},
"_app_queue_sendtouser-queue_receive_receiveExamplePayloadSendToUser": {
"action": "receive",
Expand All @@ -266,7 +276,17 @@
{
"$ref": "#/channels/_app_queue_sendtouser-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto"
}
]
],
"reply": {
"channel": {
"$ref": "#/channels/_user_queue_sendtouser-response-queue"
},
"messages": [
{
"$ref": "#/channels/_user_queue_sendtouser-response-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto"
}
]
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public SpringAnnotationOperationsScanner simpleRabbitMethodLevelListenerAnnotati
RabbitListener.class,
amqpBindingBuilder,
asyncHeadersForAmqpBuilder,
List.of(),
payloadService,
componentsService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.springframework.core.annotation.Order;
import org.springframework.jms.annotation.JmsListener;

import java.util.List;

import static io.github.springwolf.plugins.jms.configuration.properties.SpringwolfJmsConfigConstants.SPRINGWOLF_SCANNER_JMS_LISTENER_ENABLED;

/**
Expand Down Expand Up @@ -63,6 +65,7 @@ public SpringAnnotationOperationsScanner simpleJmsMethodLevelListenerAnnotationO
JmsListener.class,
jmsBindingBuilder,
new AsyncHeadersNotDocumented(),
List.of(),
payloadService,
componentsService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.springframework.kafka.annotation.KafkaHandler;
import org.springframework.kafka.annotation.KafkaListener;

import java.util.List;

import static io.github.springwolf.plugins.kafka.configuration.properties.SpringwolfKafkaConfigConstants.SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED;

/**
Expand Down Expand Up @@ -134,6 +136,7 @@ public SpringAnnotationOperationsScanner simpleKafkaMethodLevelListenerAnnotatio
KafkaListener.class,
kafkaBindingBuilder,
asyncHeadersForKafkaBuilder,
List.of(),
payloadService,
componentsService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

import java.util.List;

import static io.github.springwolf.plugins.sqs.configuration.properties.SpringwolfSqsConfigConstants.SPRINGWOLF_SCANNER_SQS_LISTENER_ENABLED;

/**
Expand Down Expand Up @@ -63,6 +65,7 @@ public SpringAnnotationOperationsScanner simpleSqsMethodLevelListenerAnnotationO
SqsListener.class,
sqsBindingBuilder,
new AsyncHeadersNotDocumented(),
List.of(),
payloadService,
componentsService);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
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.common.payload.PayloadMethodReturnService;
import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil;
import io.github.springwolf.core.asyncapi.scanners.operations.annotations.OperationCustomizer;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.bindings.StompBindingSendToFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.SendTo;

import java.lang.reflect.Method;
import java.util.List;

@RequiredArgsConstructor
public class SendToCustomizer implements OperationCustomizer {
private final StompBindingSendToFactory bindingFactory;
private final PayloadMethodReturnService payloadService;

@Override
public void customize(Operation operation, Method method) {
SendTo annotation = AnnotationScannerUtil.findAnnotation(SendTo.class, method);
if (annotation != null) {
String channelId = ReferenceUtil.toValidId(bindingFactory.getChannelName(annotation));
String payloadName = payloadService.extractSchema(method).name();

operation.setReply(OperationReply.builder()
.channel(ChannelReference.fromChannel(channelId))
.messages(List.of(MessageReference.toChannelMessage(channelId, payloadName)))
.build());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
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.common.payload.PayloadMethodReturnService;
import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil;
import io.github.springwolf.core.asyncapi.scanners.operations.annotations.OperationCustomizer;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.bindings.StompBindingSendToUserFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.simp.annotation.SendToUser;

import java.lang.reflect.Method;
import java.util.List;

@RequiredArgsConstructor
public class SendToUserCustomizer implements OperationCustomizer {
private final StompBindingSendToUserFactory bindingFactory;
private final PayloadMethodReturnService payloadService;

@Override
public void customize(Operation operation, Method method) {
SendToUser annotation = AnnotationScannerUtil.findAnnotation(SendToUser.class, method);
if (annotation != null) {
String channelId = ReferenceUtil.toValidId(bindingFactory.getChannelName(annotation));
String payloadName = payloadService.extractSchema(method).name();

operation.setReply(OperationReply.builder()
.channel(ChannelReference.fromChannel(channelId))
.messages(List.of(MessageReference.toChannelMessage(channelId, payloadName)))
.build());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import io.github.springwolf.plugins.stomp.asyncapi.scanners.bindings.StompBindingSendToFactory;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.bindings.StompBindingSendToUserFactory;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.common.header.AsyncHeadersForStompBuilder;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations.SendToCustomizer;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations.SendToUserCustomizer;
import io.github.springwolf.plugins.stomp.configuration.properties.SpringwolfStompConfigProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
Expand All @@ -26,6 +28,8 @@
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.annotation.SendToUser;

import java.util.List;

import static io.github.springwolf.plugins.stomp.configuration.properties.SpringwolfStompConfigConstants.SPRINGWOLF_SCANNER_STOMP_MESSAGE_MAPPING_ENABLED;
import static io.github.springwolf.plugins.stomp.configuration.properties.SpringwolfStompConfigConstants.SPRINGWOLF_SCANNER_STOMP_SEND_TO_ENABLED;
import static io.github.springwolf.plugins.stomp.configuration.properties.SpringwolfStompConfigConstants.SPRINGWOLF_SCANNER_STOMP_SEND_TO_USER_ENABLED;
Expand Down Expand Up @@ -70,6 +74,23 @@ public AsyncHeadersForStompBuilder stompAsyncHeadersBuilder() {
return new AsyncHeadersForStompBuilder();
}

@Bean
@ConditionalOnProperty(name = SPRINGWOLF_SCANNER_STOMP_SEND_TO_ENABLED, havingValue = "true", matchIfMissing = true)
public SendToCustomizer sendToCustomizer(
StompBindingSendToFactory bindingFactory, PayloadMethodReturnService payloadService) {
return new SendToCustomizer(bindingFactory, payloadService);
}

@Bean
@ConditionalOnProperty(
name = SPRINGWOLF_SCANNER_STOMP_SEND_TO_USER_ENABLED,
havingValue = "true",
matchIfMissing = true)
public SendToUserCustomizer sendToUserCustomizer(
StompBindingSendToUserFactory bindingFactory, PayloadMethodReturnService payloadService) {
return new SendToUserCustomizer(bindingFactory, payloadService);
}

@Bean
@ConditionalOnProperty(
name = SPRINGWOLF_SCANNER_STOMP_MESSAGE_MAPPING_ENABLED,
Expand Down Expand Up @@ -152,12 +173,15 @@ public SpringAnnotationOperationsScanner simpleStompMethodLevelListenerAnnotatio
StompBindingMessageMappingFactory stompBindingBuilder,
AsyncHeadersForStompBuilder asyncHeadersForStompBuilder,
PayloadMethodParameterService payloadService,
ComponentsService componentsService) {
ComponentsService componentsService,
SendToCustomizer sendToCustomizer,
SendToUserCustomizer sendToUserCustomizer) {
SpringAnnotationMethodLevelOperationsScanner<MessageMapping> strategy =
new SpringAnnotationMethodLevelOperationsScanner<>(
MessageMapping.class,
stompBindingBuilder,
asyncHeadersForStompBuilder,
List.of(sendToCustomizer, sendToUserCustomizer),
payloadService,
componentsService);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations;

import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodReturnService;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.bindings.StompBindingSendToFactory;
import org.junit.jupiter.api.Test;
import org.springframework.messaging.handler.annotation.SendTo;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class SendToCustomizerTest {

private static final String CHANNEL_ID = "queue";
private static final String MESSAGE_ID = "schema-name";

private StompBindingSendToFactory bindingFactory = mock(StompBindingSendToFactory.class);
private PayloadMethodReturnService payloadService = mock(PayloadMethodReturnService.class);

private SendToCustomizer sendToCustomizer = new SendToCustomizer(bindingFactory, payloadService);

@Test
void customize() throws NoSuchMethodException {
// given
Operation operation = new Operation();
when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID);
when(payloadService.extractSchema(any())).thenReturn(new NamedSchemaObject(MESSAGE_ID, null));

// when
sendToCustomizer.customize(operation, this.getClass().getDeclaredMethod("testMethod"));

// then
assertThat(operation.getReply()).isNotNull();
assertThat(operation.getReply().getChannel()).isEqualTo(ChannelReference.fromChannel(CHANNEL_ID));
assertThat(operation.getReply().getMessages())
.isEqualTo(List.of(MessageReference.toChannelMessage(CHANNEL_ID, MESSAGE_ID)));
}

@SendTo(CHANNEL_ID)
void testMethod() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations;

import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodReturnService;
import io.github.springwolf.plugins.stomp.asyncapi.scanners.bindings.StompBindingSendToUserFactory;
import org.junit.jupiter.api.Test;
import org.springframework.messaging.simp.annotation.SendToUser;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class SendToUserCustomizerTest {

private static final String CHANNEL_ID = "queue";
private static final String MESSAGE_ID = "schema-name";

private StompBindingSendToUserFactory bindingFactory = mock(StompBindingSendToUserFactory.class);
private PayloadMethodReturnService payloadService = mock(PayloadMethodReturnService.class);

private SendToUserCustomizer sendToCustomizer = new SendToUserCustomizer(bindingFactory, payloadService);

@Test
void customize() throws NoSuchMethodException {
// given
Operation operation = new Operation();
when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID);
when(payloadService.extractSchema(any())).thenReturn(new NamedSchemaObject(MESSAGE_ID, null));

// when
sendToCustomizer.customize(operation, this.getClass().getDeclaredMethod("testMethod"));

// then
assertThat(operation.getReply()).isNotNull();
assertThat(operation.getReply().getChannel()).isEqualTo(ChannelReference.fromChannel(CHANNEL_ID));
assertThat(operation.getReply().getMessages())
.isEqualTo(List.of(MessageReference.toChannelMessage(CHANNEL_ID, MESSAGE_ID)));
}

@SendToUser(CHANNEL_ID)
void testMethod() {}
}

0 comments on commit 2e2a623

Please sign in to comment.