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:
+ *
+ * - {@link Message} to get access to the complete message being processed.
+ * - {@link Payload}-annotated method arguments to extract the payload of
+ * a message and optionally convert it using a
+ * {@link org.springframework.messaging.support.converter.MessageConverter}.
+ * The presence of the annotation is not required since it is assumed by default
+ * for method arguments that are not annotated.
+ * - {@link Header}-annotated method arguments to extract a specific
+ * header value along with type conversion with a
+ * {@link org.springframework.core.convert.converter.Converter} if necessary.
+ * - {@link Headers}-annotated argument that must also be assignable to
+ * {@link java.util.Map} for getting access to all headers.
+ * - {@link org.springframework.messaging.MessageHeaders} arguments for
+ * getting access to all headers.
+ * - {@link org.springframework.messaging.support.MessageHeaderAccessor} or
+ * with STOMP over WebSocket support also sub-classes such as
+ * {@link org.springframework.messaging.simp.SimpMessageHeaderAccessor}
+ * for convenient access to all method arguments.
+ * - {@link PathVariable}-annotated arguments for access to URI variable
+ * values extracted from the message destination (i.e. /hotels/{hotel}).
+ * Variable values will be converted to the declared method argument type.
+ * - {@link java.security.Principal} method arguments are supported with
+ * STOMP over WebSocket messages. It reflects the user logged in to the
+ * WebSocket session on which the message was received. Regular HTTP-based
+ * authentication (e.g. Spring Security based) can be used to secure the
+ * HTTP handshake that initiates WebSocket sessions.
+ *
+ *
+ * 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);
}