From 35b9b8400beb25d9f2de4225e5a40489427043a5 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 15 Nov 2013 16:32:52 +0000 Subject: [PATCH] Rename @SubscribeEvent to @SubscribeMapping Also @SubscribeMapping is now a method-level annotation only that can be used in combination with a type-level @MessageMapping. This method also documents supported method arguments and return value types on @Subscribe- and @MessageMapping methods. --- .../handler/annotation/MessageMapping.java | 50 ++++++++++++++++++- ...scribeEvent.java => SubscribeMapping.java} | 23 +++++++-- .../SubscriptionMethodReturnValueHandler.java | 6 +-- .../SimpAnnotationMethodMessageHandler.java | 19 ++++--- .../SimpMessageTypeMessageCondition.java | 33 ++++++------ ...criptionMethodReturnValueHandlerTests.java | 6 +-- ...essageBrokerConfigurationSupportTests.java | 4 +- ...mpAnnotationMethodMessageHandlerTests.java | 17 +++---- .../SimpMessageTypeMessageConditionTests.java | 24 ++------- 9 files changed, 110 insertions(+), 72 deletions(-) rename spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/{SubscribeEvent.java => SubscribeMapping.java} (55%) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java index 62b3b889a6..7b91921aa9 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java @@ -26,8 +26,54 @@ import org.springframework.messaging.Message; /** - * Annotation for mapping a {@link Message} onto message handling methods by matching to - * the message destination. + * Annotation for mapping a {@link Message} onto message-handling methods by matching + * to the message destination. This annotation can also be used on the type-level in + * which case it defines a common destination prefix or pattern for all method-level + * annotations including method-level + * {@link org.springframework.messaging.simp.annotation.SubscribeMapping @SubscribeMapping} + * annotations. + *

+ * Handler methods which are annotated with this annotation are allowed to have + * flexible signatures. They may have arguments of the following types, in arbitrary + * order: + *

+ *

+ * By default the return value is wrapped as a message and sent to the destination + * specified with an {@link SendTo} method-level annotation. + *

+ * STOMP over WebSocket: an {@link SendTo} annotation is not strictly required -- + * by default the message will be sent to the same destination as the incoming + * message but with an additional prefix ("/topic" by default). It is also possible + * to use {@link org.springframework.messaging.simp.annotation.SendToUser} to + * have the message directed to a specific user only if connected. + * Also the return value is converted with a + * {@link org.springframework.messaging.support.converter.MessageConverter}. * * @author Rossen Stoyanchev * @since 4.0 diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeEvent.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java similarity index 55% rename from spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeEvent.java rename to spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java index 68b8359c51..3ffe6ba056 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeEvent.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java @@ -24,16 +24,31 @@ import java.lang.annotation.Target; /** - * Annotation for mapping subscription events onto specific handler methods based - * on the destination for the message (e.g. STOMP SUBSCRIBE message). + * Annotation for mapping subscription messages onto specific handler methods based + * on the destination of a subscription. Supported with STOMP over WebSocket only + * (e.g. STOMP SUBSCRIBE frame). + *

+ * This is a method-level annotations that can be combined with a type-level + * {@link org.springframework.messaging.handler.annotation.MessageMapping @MessageMapping} + *

+ * Supports the same method arguments as + * {@link org.springframework.messaging.handler.annotation.MessageMapping}, however + * subscription messages typically do not have a body. + *

+ * The return value also follows the same rules as for + * {@link org.springframework.messaging.handler.annotation.MessageMapping} except if + * the method is not annotated with + * {@link org.springframework.messaging.handler.annotation.SendTo} or {@link SendToUser}, + * the message is sent directly back to the connected user and does not pass through + * the message broker. This is useful for implementing a request-reply pattern. * * @author Rossen Stoyanchev * @since 4.0 */ -@Target({ElementType.TYPE, ElementType.METHOD}) +@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented -public @interface SubscribeEvent { +public @interface SubscribeMapping { /** * Destination-based mapping expressed by this annotation. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java index 164c10e028..e9b885d4cc 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java @@ -25,14 +25,14 @@ import org.springframework.messaging.handler.method.HandlerMethodReturnValueHand import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.annotation.SendToUser; -import org.springframework.messaging.simp.annotation.SubscribeEvent; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.Assert; /** * A {@link HandlerMethodReturnValueHandler} for replying directly to a subscription. It - * supports methods annotated with {@link SubscribeEvent} unless they're also annotated + * supports methods annotated with {@link org.springframework.messaging.simp.annotation.SubscribeMapping} unless they're also annotated * with {@link SendTo} or {@link SendToUser}. *

* The value returned from the method is converted, and turned to a {@link Message} and @@ -55,7 +55,7 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn @Override public boolean supportsReturnType(MethodParameter returnType) { - return ((returnType.getMethodAnnotation(SubscribeEvent.class) != null) + return ((returnType.getMethodAnnotation(SubscribeMapping.class) != null) && (returnType.getMethodAnnotation(SendTo.class) == null) && (returnType.getMethodAnnotation(SendToUser.class) == null)); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandler.java index 8a990af157..0c2669658f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandler.java @@ -49,7 +49,7 @@ import org.springframework.messaging.handler.method.HandlerMethodReturnValueHand import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.messaging.simp.annotation.SubscribeEvent; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.simp.annotation.support.PrincipalMethodArgumentResolver; import org.springframework.messaging.simp.annotation.support.SendToMethodReturnValueHandler; import org.springframework.messaging.simp.annotation.support.SubscriptionMethodReturnValueHandler; @@ -66,7 +66,7 @@ import org.springframework.util.PathMatcher; /** - * A handler for messages delegating to {@link SubscribeEvent @SubscribeEvent} and + * A handler for messages delegating to {@link org.springframework.messaging.simp.annotation.SubscribeMapping @SubscribeMapping} and * {@link MessageMapping @MessageMapping} annotated methods. *

* Supports Ant-style path patterns as well as URI template variables in destinations. @@ -214,22 +214,21 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan @Override protected SimpMessageMappingInfo getMappingForMethod(Method method, Class handlerType) { - MessageMapping messageMappingAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class); - if (messageMappingAnnot != null) { - SimpMessageMappingInfo result = createMessageMappingCondition(messageMappingAnnot); - MessageMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class); + MessageMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class); + MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class); + if (messageAnnot != null) { + SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot); if (typeAnnot != null) { result = createMessageMappingCondition(typeAnnot).combine(result); } return result; } - SubscribeEvent subsribeAnnot = AnnotationUtils.findAnnotation(method, SubscribeEvent.class); + SubscribeMapping subsribeAnnot = AnnotationUtils.findAnnotation(method, SubscribeMapping.class); if (subsribeAnnot != null) { SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnot); - SubscribeEvent typeAnnot = AnnotationUtils.findAnnotation(handlerType, SubscribeEvent.class); if (typeAnnot != null) { - result = createSubscribeCondition(typeAnnot).combine(result); + result = createMessageMappingCondition(typeAnnot).combine(result); } return result; } @@ -242,7 +241,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan new DestinationPatternsMessageCondition(annotation.value())); } - private SimpMessageMappingInfo createSubscribeCondition(SubscribeEvent annotation) { + private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) { return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE, new DestinationPatternsMessageCondition(annotation.value())); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageCondition.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageCondition.java index ea7b471b7d..582ed40c5c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageCondition.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageCondition.java @@ -24,7 +24,6 @@ import org.springframework.util.Assert; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; /** * A message condition that checks the message type. @@ -44,13 +43,6 @@ public class SimpMessageTypeMessageCondition extends AbstractMessageCondition getContent() { - return (this.messageType != null) ? Arrays.asList(messageType) : Collections.emptyList(); + return Arrays.asList(messageType); } @Override @@ -78,7 +70,7 @@ public class SimpMessageTypeMessageCondition extends AbstractMessageCondition message) { - if ((this.messageType == null) && (other.messageType == null)) { - return 0; - } - if (this.messageType == null) { - return 1; - } - if (other.messageType == null) { - return -1; + Object actualMessageType = message.getHeaders().get(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER); + if (actualMessageType != null) { + if (actualMessageType.equals(this.getMessageType()) && actualMessageType.equals(other.getMessageType())) { + return 0; + } + else if (actualMessageType.equals(this.getMessageType())) { + return -1; + } + else if (actualMessageType.equals(other.getMessageType())) { + return 1; + } } return 0; } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java index f6bcfed34f..8b9c5b0bcb 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandlerTests.java @@ -32,7 +32,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.messaging.simp.annotation.SubscribeEvent; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.converter.MessageConverter; @@ -132,12 +132,12 @@ public class SubscriptionMethodReturnValueHandlerTests { } - @SubscribeEvent("/data") // not needed for the tests but here for completeness + @SubscribeMapping("/data") // not needed for the tests but here for completeness private String getData() { return payloadContent; } - @SubscribeEvent("/data") // not needed for the tests but here for completeness + @SubscribeMapping("/data") // not needed for the tests but here for completeness @SendTo("/sendToDest") private String getDataAndSendTo() { return payloadContent; diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/WebSocketMessageBrokerConfigurationSupportTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/WebSocketMessageBrokerConfigurationSupportTests.java index 8103cc36d9..af68277a36 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/WebSocketMessageBrokerConfigurationSupportTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/WebSocketMessageBrokerConfigurationSupportTests.java @@ -27,7 +27,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.handler.websocket.SubProtocolWebSocketHandler; import org.springframework.messaging.simp.SimpMessageType; -import org.springframework.messaging.simp.annotation.SubscribeEvent; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.simp.handler.SimpAnnotationMethodMessageHandler; import org.springframework.messaging.simp.handler.SimpleBrokerMessageHandler; import org.springframework.messaging.simp.handler.UserDestinationMessageHandler; @@ -262,7 +262,7 @@ public class WebSocketMessageBrokerConfigurationSupportTests { @Controller static class TestController { - @SubscribeEvent("/foo") + @SubscribeMapping("/foo") public String handleSubscribe() { return "bar"; } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandlerTests.java index bf98397378..6f9e757ae5 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpAnnotationMethodMessageHandlerTests.java @@ -33,7 +33,7 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.messaging.simp.annotation.SubscribeEvent; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Controller; @@ -121,7 +121,7 @@ public class SimpAnnotationMethodMessageHandlerTests { @Test public void bestMatchWildcard() { - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); headers.setDestination("/pre/bestmatch/bar/path"); Message message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build(); this.messageHandler.handleMessage(message); @@ -159,7 +159,6 @@ public class SimpAnnotationMethodMessageHandlerTests { @Controller @MessageMapping("/pre") - @SubscribeEvent("/pre") private static class TestController { private String method; @@ -176,15 +175,15 @@ public class SimpAnnotationMethodMessageHandlerTests { @MessageMapping("/message/{foo}/{name}") public void messageMappingPathVariable(@PathVariable("foo") String param1, - @PathVariable("name") String param2) { + @PathVariable("name") String param2) { this.method = "messageMappingPathVariable"; this.arguments.put("foo", param1); this.arguments.put("name", param2); } - @SubscribeEvent("/sub/{foo}/{name}") + @SubscribeMapping("/sub/{foo}/{name}") public void subscribeEventPathVariable(@PathVariable("foo") String param1, - @PathVariable("name") String param2) { + @PathVariable("name") String param2) { this.method = "subscribeEventPathVariable"; this.arguments.put("foo", param1); this.arguments.put("name", param2); @@ -201,9 +200,9 @@ public class SimpAnnotationMethodMessageHandlerTests { this.arguments.put("foo", param1); } - @MessageMapping("/bestmatch/**") - public void otherMatch() { - this.method = "otherMatch"; + @MessageMapping("/bestmatch/*/*") + public void secondBestMatch() { + this.method = "secondBestMatch"; } @MessageMapping("/binding/id/{id}") diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageConditionTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageConditionTests.java index 3358a789ef..0b28f83cdc 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageConditionTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/SimpMessageTypeMessageConditionTests.java @@ -31,24 +31,13 @@ import static org.junit.Assert.*; */ public class SimpMessageTypeMessageConditionTests { - @Test - public void combineEmptySets() { - SimpMessageTypeMessageCondition c1 = condition(); - SimpMessageTypeMessageCondition c2 = condition(); - - assertNull(c1.combine(c2).getMessageType()); - } - @Test public void combine() { - SimpMessageType actual = condition().combine(condition()).getMessageType(); - assertNull(actual); - - actual = condition().combine(condition(SimpMessageType.SUBSCRIBE)).getMessageType(); + SimpMessageType actual = condition(SimpMessageType.MESSAGE).combine(condition(SimpMessageType.SUBSCRIBE)).getMessageType(); assertEquals(SimpMessageType.SUBSCRIBE, actual); - actual = condition(SimpMessageType.SUBSCRIBE).combine(condition()).getMessageType(); - assertEquals(SimpMessageType.SUBSCRIBE, actual); + actual = condition(SimpMessageType.MESSAGE).combine(condition(SimpMessageType.MESSAGE)).getMessageType(); + assertEquals(SimpMessageType.MESSAGE, actual); actual = condition(SimpMessageType.SUBSCRIBE).combine(condition(SimpMessageType.SUBSCRIBE)).getMessageType(); assertEquals(SimpMessageType.SUBSCRIBE, actual); @@ -75,9 +64,8 @@ public class SimpMessageTypeMessageConditionTests { @Test public void compareTo() { Message message = message(null); - assertEquals(1, condition().compareTo(condition(SimpMessageType.MESSAGE), message)); - assertEquals(-1, condition(SimpMessageType.MESSAGE).compareTo(condition(), message)); assertEquals(0, condition(SimpMessageType.MESSAGE).compareTo(condition(SimpMessageType.MESSAGE), message)); + assertEquals(0, condition(SimpMessageType.MESSAGE).compareTo(condition(SimpMessageType.SUBSCRIBE), message)); } private Message message(SimpMessageType messageType) { @@ -88,10 +76,6 @@ public class SimpMessageTypeMessageConditionTests { return builder.build(); } - private SimpMessageTypeMessageCondition condition() { - return new SimpMessageTypeMessageCondition(); - } - private SimpMessageTypeMessageCondition condition(SimpMessageType messageType) { return new SimpMessageTypeMessageCondition(messageType); }