diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/AbstractMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/AbstractMethodReturnValueHandler.java new file mode 100644 index 000000000000..df4d57ecfd88 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/AbstractMethodReturnValueHandler.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.messaging.simp.annotation.support; + +import java.security.Principal; + +import org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.handler.DestinationPatternsMessageCondition; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.messaging.simp.SimpMessageType; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.messaging.simp.user.DestinationUserNameProvider; +import org.springframework.messaging.support.MessageHeaderInitializer; +import org.springframework.util.Assert; + + +/** + * Abstract {@link HandlerMethodReturnValueHandler} + * + * @author Sergi Almar + * @since 4.1.1 + */ +public abstract class AbstractMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + protected final SimpMessageSendingOperations messagingTemplate; + + protected String defaultDestinationPrefix = "/topic"; + + protected String defaultUserDestinationPrefix = "/queue"; + + protected MessageHeaderInitializer headerInitializer; + + public AbstractMethodReturnValueHandler(SimpMessageSendingOperations messagingTemplate) { + Assert.notNull(messagingTemplate, "messagingTemplate must not be null"); + this.messagingTemplate = messagingTemplate; + } + + /** + * Configure a default prefix to add to message destinations in cases where a method + * is not annotated with {@link SendTo @SendTo} or does not specify any destinations + * through the annotation's value attribute. + *

By default, the prefix is set to "/topic". + */ + public void setDefaultDestinationPrefix(String defaultDestinationPrefix) { + this.defaultDestinationPrefix = defaultDestinationPrefix; + } + + /** + * Return the configured default destination prefix. + * @see #setDefaultDestinationPrefix(String) + */ + public String getDefaultDestinationPrefix() { + return this.defaultDestinationPrefix; + } + + /** + * Configure a default prefix to add to message destinations in cases where a + * method is annotated with {@link SendToUser @SendToUser} but does not specify + * any destinations through the annotation's value attribute. + *

By default, the prefix is set to "/queue". + */ + public void setDefaultUserDestinationPrefix(String prefix) { + this.defaultUserDestinationPrefix = prefix; + } + + /** + * Return the configured default user destination prefix. + * @see #setDefaultUserDestinationPrefix(String) + */ + public String getDefaultUserDestinationPrefix() { + return this.defaultUserDestinationPrefix; + } + + /** + * Configure a {@link MessageHeaderInitializer} to apply to the headers of all + * messages sent to the client outbound channel. + * + *

By default this property is not set. + */ + public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) { + this.headerInitializer = headerInitializer; + } + + /** + * @return the configured header initializer. + */ + public MessageHeaderInitializer getHeaderInitializer() { + return this.headerInitializer; + } + + public final void handleReturnValue(Object returnValue, MethodParameter returnType, Message message) throws Exception { + if (returnValue == null) { + return; + } + + handleReturnValueInternal(returnValue, returnType, message); + } + + public abstract void handleReturnValueInternal(Object returnValue, MethodParameter returnType, Message message) throws Exception ; + + protected String getUserName(Message message, MessageHeaders headers) { + Principal principal = SimpMessageHeaderAccessor.getUser(headers); + if (principal != null) { + return (principal instanceof DestinationUserNameProvider ? + ((DestinationUserNameProvider) principal).getDestinationUserName() : principal.getName()); + } + return null; + } + + protected String[] getTargetDestinations(Message message, String defaultPrefix) { + String name = DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER; + String destination = (String) message.getHeaders().get(name); + Assert.hasText(destination, "No lookup destination header in " + message); + + return (destination.startsWith("/") ? + new String[] {defaultPrefix + destination} : new String[] {defaultPrefix + "/" + destination}); + } + + protected MessageHeaders createHeaders(String sessionId) { + SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); + if (getHeaderInitializer() != null) { + getHeaderInitializer().initHeaders(headerAccessor); + } + headerAccessor.setSessionId(sessionId); + headerAccessor.setLeaveMutable(true); + return headerAccessor.getMessageHeaders(); + } +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/ResponseMessageMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/ResponseMessageMethodReturnValueHandler.java new file mode 100644 index 000000000000..6fa757c610db --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/ResponseMessageMethodReturnValueHandler.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.messaging.simp.annotation.support; + +import org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.messaging.support.ResponseMessage; +import org.springframework.util.ObjectUtils; + + +/** + * A {@link HandlerMethodReturnValueHandler} for sending messages according to the + * {@link org.springframework.messaging.support.ResponseMessage} returned from message handling methods. + * + * @author Sergi Almar + * @since 4.1.1 + */ +public class ResponseMessageMethodReturnValueHandler extends AbstractMethodReturnValueHandler { + + public ResponseMessageMethodReturnValueHandler(SimpMessageSendingOperations messagingTemplate) { + super(messagingTemplate); + } + + @Override + public boolean supportsReturnType(MethodParameter returnType) { + return returnType.getParameterType().equals(ResponseMessage.class); + } + + @Override + public void handleReturnValueInternal(Object returnValue, MethodParameter returnType, Message message) throws Exception { + ResponseMessage responseMessage = (ResponseMessage) returnValue; + + boolean toUniqueUser = responseMessage.getUser() != null || responseMessage.isToCurrentUser(); + + if(toUniqueUser) { + handleUserDestinations(responseMessage, message); + } + else { + handleGenericDestinations(responseMessage, message); + } + } + + private void handleGenericDestinations(ResponseMessage responseMessage, Message message) { + String [] destinations = getTargetDestinations(responseMessage, message, getDefaultDestinationPrefix()); + + MessageHeaders headers = message.getHeaders(); + String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); + + for (String destination : destinations) { + this.messagingTemplate.convertAndSend(destination, responseMessage.getBody(), createHeaders(sessionId)); + } + } + + private void handleUserDestinations(ResponseMessage responseMessage, Message message) { + String [] destinations = getTargetDestinations(responseMessage, message, getDefaultUserDestinationPrefix()); + String user = responseMessage.getUser(); + boolean broadcast = responseMessage.isBroadcast(); + + MessageHeaders headers = message.getHeaders(); + String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); + + if(responseMessage.isToCurrentUser()) { + user = super.getUserName(message, headers); + + if (user == null) { + if (sessionId == null) { + throw new MissingSessionUserException(message); + } + + user = sessionId; + broadcast = false; + } + } + + for (String destination : destinations) { + if(broadcast && responseMessage.isToCurrentUser()) { + this.messagingTemplate.convertAndSendToUser(user, destination, responseMessage.getBody()); + } + else { + this.messagingTemplate.convertAndSendToUser(user, destination, responseMessage.getBody(), createHeaders(sessionId)); + } + } + } + + protected String[] getTargetDestinations(ResponseMessage responseMessage, Message message, String defaultPrefix) { + String [] destinations = responseMessage.getDestinations(); + + if (!ObjectUtils.isEmpty(destinations)) { + return destinations; + } + + return super.getTargetDestinations(message, defaultPrefix); + } +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java index fc70b9ba3910..8c9d8d231b6f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java @@ -49,80 +49,15 @@ * @author Rossen Stoyanchev * @since 4.0 */ -public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - - private final SimpMessageSendingOperations messagingTemplate; +public class SendToMethodReturnValueHandler extends AbstractMethodReturnValueHandler { private final boolean annotationRequired; - private String defaultDestinationPrefix = "/topic"; - - private String defaultUserDestinationPrefix = "/queue"; - - private MessageHeaderInitializer headerInitializer; - - public SendToMethodReturnValueHandler(SimpMessageSendingOperations messagingTemplate, boolean annotationRequired) { - Assert.notNull(messagingTemplate, "messagingTemplate must not be null"); - this.messagingTemplate = messagingTemplate; + super(messagingTemplate); this.annotationRequired = annotationRequired; } - - /** - * Configure a default prefix to add to message destinations in cases where a method - * is not annotated with {@link SendTo @SendTo} or does not specify any destinations - * through the annotation's value attribute. - *

By default, the prefix is set to "/topic". - */ - public void setDefaultDestinationPrefix(String defaultDestinationPrefix) { - this.defaultDestinationPrefix = defaultDestinationPrefix; - } - - /** - * Return the configured default destination prefix. - * @see #setDefaultDestinationPrefix(String) - */ - public String getDefaultDestinationPrefix() { - return this.defaultDestinationPrefix; - } - - /** - * Configure a default prefix to add to message destinations in cases where a - * method is annotated with {@link SendToUser @SendToUser} but does not specify - * any destinations through the annotation's value attribute. - *

By default, the prefix is set to "/queue". - */ - public void setDefaultUserDestinationPrefix(String prefix) { - this.defaultUserDestinationPrefix = prefix; - } - - /** - * Return the configured default user destination prefix. - * @see #setDefaultUserDestinationPrefix(String) - */ - public String getDefaultUserDestinationPrefix() { - return this.defaultUserDestinationPrefix; - } - - /** - * Configure a {@link MessageHeaderInitializer} to apply to the headers of all - * messages sent to the client outbound channel. - * - *

By default this property is not set. - */ - public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) { - this.headerInitializer = headerInitializer; - } - - /** - * @return the configured header initializer. - */ - public MessageHeaderInitializer getHeaderInitializer() { - return this.headerInitializer; - } - - @Override public boolean supportsReturnType(MethodParameter returnType) { if ((returnType.getMethodAnnotation(SendTo.class) != null) || @@ -133,10 +68,7 @@ public boolean supportsReturnType(MethodParameter returnType) { } @Override - public void handleReturnValue(Object returnValue, MethodParameter returnType, Message message) throws Exception { - if (returnValue == null) { - return; - } + public void handleReturnValueInternal(Object returnValue, MethodParameter returnType, Message message) throws Exception { MessageHeaders headers = message.getHeaders(); String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); @@ -170,15 +102,6 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType, Me } } - protected String getUserName(Message message, MessageHeaders headers) { - Principal principal = SimpMessageHeaderAccessor.getUser(headers); - if (principal != null) { - return (principal instanceof DestinationUserNameProvider ? - ((DestinationUserNameProvider) principal).getDestinationUserName() : principal.getName()); - } - return null; - } - protected String[] getTargetDestinations(Annotation annotation, Message message, String defaultPrefix) { if (annotation != null) { String[] value = (String[]) AnnotationUtils.getValue(annotation); @@ -186,25 +109,10 @@ protected String[] getTargetDestinations(Annotation annotation, Message messa return value; } } - String name = DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER; - String destination = (String) message.getHeaders().get(name); - Assert.hasText(destination, "No lookup destination header in " + message); - - return (destination.startsWith("/") ? - new String[] {defaultPrefix + destination} : new String[] {defaultPrefix + "/" + destination}); - } - private MessageHeaders createHeaders(String sessionId) { - SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); - if (getHeaderInitializer() != null) { - getHeaderInitializer().initHeaders(headerAccessor); - } - headerAccessor.setSessionId(sessionId); - headerAccessor.setLeaveMutable(true); - return headerAccessor.getMessageHeaders(); + return super.getTargetDestinations(message, defaultPrefix); } - @Override public String toString() { return "SendToMethodReturnValueHandler [annotationRequired=" + annotationRequired + "]"; diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java index 49120c763b4b..5b307ad72b54 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java @@ -327,6 +327,11 @@ protected List initReturnValueHandler SubscriptionMethodReturnValueHandler sh = new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate); sh.setHeaderInitializer(this.headerInitializer); handlers.add(sh); + + // ResponseMessage return type + ResponseMessageMethodReturnValueHandler rmh = new ResponseMessageMethodReturnValueHandler(this.brokerTemplate); + rmh.setHeaderInitializer(this.headerInitializer); + handlers.add(rmh); // custom return value types handlers.addAll(getCustomReturnValueHandlers()); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/ResponseMessage.java b/spring-messaging/src/main/java/org/springframework/messaging/support/ResponseMessage.java new file mode 100644 index 000000000000..5024b1f53f82 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/support/ResponseMessage.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.messaging.support; + + +/** + * + * @author Sergi Almar + * @since 4.1 + */ +public class ResponseMessage { + + private String user; + private String [] destinations; + private T body; + private boolean toCurrentUser = false; + private boolean broadcast = true; + + public ResponseMessage(T body) { + this.body = body; + } + + public ResponseMessage(T body, String... destinations) { + this(body); + this.destinations = destinations; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String[] getDestinations() { + return destinations; + } + + public void setDestinations(String[] destinations) { + this.destinations = destinations; + } + + public T getBody() { + return body; + } + + public void setBody(T body) { + this.body = body; + } + + public boolean isToCurrentUser() { + return toCurrentUser; + } + + public void setToCurrentUser(boolean toCurrentUser) { + this.toCurrentUser = toCurrentUser; + } + + public boolean isBroadcast() { + return broadcast; + } + + public void setBroadcast(boolean broadcast) { + this.broadcast = broadcast; + } + + public static ResponseMessageBuilder destinations(String... destination) { + return new DefaultResponseDestinationBuilder(destination); + } + + public static ResponseMessageBuilder destination(String destination) { + return destinations(new String[] { destination }); + } + + public interface ResponseMessageBuilder { + ResponseMessageBuilder toUser(String username); + ResponseMessageBuilder toCurrentUser(); + ResponseMessageBuilder toCurrentUserNoBroadcast(); + ResponseMessage body(T body); + } + + private static class DefaultResponseDestinationBuilder implements ResponseMessageBuilder { + + private String [] destinations; + private String user; + private boolean toCurrentUser = false; + private boolean broadcast = true; + + public DefaultResponseDestinationBuilder(String... destinations) { + this.destinations = destinations; + } + + public ResponseMessageBuilder toUser(String user) { + this.user = user; + this.toCurrentUser = false; + return this; + } + + public ResponseMessageBuilder toCurrentUser() { + this.toCurrentUser = true; + this.user = null; + return this; + } + + public ResponseMessageBuilder toCurrentUserNoBroadcast() { + this.toCurrentUser(); + this.broadcast = false; + return this; + } + + public ResponseMessage body(T body) { + ResponseMessage responseMessage = new ResponseMessage(body, destinations); + responseMessage.setUser(user); + responseMessage.setBroadcast(broadcast); + responseMessage.setToCurrentUser(toCurrentUser); + return responseMessage; + } + } +} \ No newline at end of file diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/ResponseMessageMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/ResponseMessageMethodReturnValueHandlerTests.java new file mode 100644 index 000000000000..03b779a19c81 --- /dev/null +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/ResponseMessageMethodReturnValueHandlerTests.java @@ -0,0 +1,273 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.messaging.simp.annotation.support; + +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.converter.StringMessageConverter; +import org.springframework.messaging.handler.DestinationPatternsMessageCondition; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.messaging.support.ResponseMessage; +import org.springframework.util.MimeType; + +import static org.junit.Assert.*; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + + +/** + * Test fixture for {@link ResponseMessageMethodReturnValueHandler} + * + * @author Sergi Almar + */ +public class ResponseMessageMethodReturnValueHandlerTests { + + public static final MimeType MIME_TYPE = new MimeType("text", "plain", Charset.forName("UTF-8")); + + private static final String PAYLOAD = "payload"; + + private static final String USERNAME = "sergi"; + + + private ResponseMessageMethodReturnValueHandler handler; + + @Mock private MessageChannel messageChannel; + + @Captor ArgumentCaptor> messageCaptor; + + private MethodParameter responseMessageDestinationReturnType; + private MethodParameter responseMessageDefaultDestinationReturnType; + private MethodParameter responseMessageUserDestinationReturnType; + private MethodParameter responseMessageUserDestinationMultipleReturnType; + private MethodParameter responseMessageCurrentUserDestinationReturnType; + private MethodParameter responseMessageCurrentUserSingleSessionDestinationReturnType; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel); + messagingTemplate.setMessageConverter(new StringMessageConverter()); + + this.handler = new ResponseMessageMethodReturnValueHandler(messagingTemplate); + + Method method = this.getClass().getDeclaredMethod("handleAndSendToDestination"); + this.responseMessageDestinationReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("handleAndSendToDefaultDestination"); + this.responseMessageDefaultDestinationReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("handleAndSendToUser"); + this.responseMessageUserDestinationReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("handleAndSendToUserMultiple"); + this.responseMessageUserDestinationMultipleReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("handleAndSendToCurrentUser"); + this.responseMessageCurrentUserDestinationReturnType = new MethodParameter(method, -1); + + method = this.getClass().getDeclaredMethod("handleAndSendToCurrentUser"); + this.responseMessageCurrentUserSingleSessionDestinationReturnType = new MethodParameter(method, -1); + } + + @Test + public void supportsReturnType() throws Exception { + assertTrue(this.handler.supportsReturnType(this.responseMessageDestinationReturnType)); + } + + @Test + public void testResponseMessageDestination() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null); + ResponseMessage responseMessage = new ResponseMessage(PAYLOAD, "/topic/dest1"); + this.handler.handleReturnValue(responseMessage, this.responseMessageDestinationReturnType, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + + Message message = this.messageCaptor.getAllValues().get(0); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals(sessionId, headers.getSessionId()); + assertEquals("/topic/dest1", headers.getDestination()); + assertEquals(MIME_TYPE, headers.getContentType()); + assertNull("Subscription id should not be copied", headers.getSubscriptionId()); + } + + @Test + public void testResponseMessageDefaultDestination() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null); + ResponseMessage responseMessage = new ResponseMessage(PAYLOAD); + this.handler.handleReturnValue(responseMessage, this.responseMessageDefaultDestinationReturnType, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + + Message message = this.messageCaptor.getAllValues().get(0); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals("/topic/dest", headers.getDestination()); + } + + @Test + public void testResponseMessageUserDestination() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null); + ResponseMessage responseMessage = ResponseMessage.destination("/queue/dest1").toUser(USERNAME).body(PAYLOAD); + this.handler.handleReturnValue(responseMessage, this.responseMessageUserDestinationReturnType, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + + Message message = this.messageCaptor.getAllValues().get(0); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals("/user/" + USERNAME + "/queue/dest1", headers.getDestination()); + assertEquals(sessionId, headers.getSessionId()); + } + + @Test + public void testResponseMessageUserDestinationMultiple() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null); + ResponseMessage responseMessage = ResponseMessage.destinations("/queue/dest1", "/queue/dest2").toUser(USERNAME).body(PAYLOAD); + this.handler.handleReturnValue(responseMessage, this.responseMessageUserDestinationMultipleReturnType, inputMessage); + + verify(this.messageChannel, times(2)).send(this.messageCaptor.capture()); + + Message message = this.messageCaptor.getAllValues().get(0); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals("/user/" + USERNAME + "/queue/dest1", headers.getDestination()); + assertEquals(sessionId, headers.getSessionId()); + + message = this.messageCaptor.getAllValues().get(1); + headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals("/user/" + USERNAME + "/queue/dest2", headers.getDestination()); + assertEquals(sessionId, headers.getSessionId()); + } + + @Test + public void testResponseMessageCurrentUserDestination() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + TestUser user = new TestUser(); + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", user); + ResponseMessage responseMessage = ResponseMessage.destination("/queue/dest1").toCurrentUser().body(PAYLOAD); + this.handler.handleReturnValue(responseMessage, this.responseMessageCurrentUserDestinationReturnType, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + + Message message = this.messageCaptor.getAllValues().get(0); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals("/user/" + user.getName() + "/queue/dest1", headers.getDestination()); + assertNull(headers.getSessionId()); + assertNull(headers.getSubscriptionId()); + } + + @Test + public void testResponseMessageCurrentUserSingleSessionDestination() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + TestUser user = new TestUser(); + Message inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", user); + ResponseMessage responseMessage = ResponseMessage.destination("/queue/dest1").toCurrentUserNoBroadcast().body(PAYLOAD); + this.handler.handleReturnValue(responseMessage, this.responseMessageCurrentUserSingleSessionDestinationReturnType, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + + Message message = this.messageCaptor.getAllValues().get(0); + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message); + assertEquals("/user/" + user.getName() + "/queue/dest1", headers.getDestination()); + assertEquals(sessionId, headers.getSessionId()); + assertEquals(MIME_TYPE, headers.getContentType()); + assertNull("Subscription id should not be copied", headers.getSubscriptionId()); + } + + private Message createInputMessage(String sessId, String subsId, String destinationPrefix, + String destination, Principal principal) { + + SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(); + headerAccessor.setSessionId(sessId); + headerAccessor.setSubscriptionId(subsId); + if (destination != null && destinationPrefix != null) { + headerAccessor.setDestination(destinationPrefix + destination); + headerAccessor.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, destination); + } + if (principal != null) { + headerAccessor.setUser(principal); + } + return MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders()); + } + + private static class TestUser implements Principal { + + public String getName() { + return "joe"; + } + + public boolean implies(Subject subject) { + return false; + } + } + + public ResponseMessage handleAndSendToDestination() { + return new ResponseMessage(PAYLOAD, "/topic/dest1"); + } + + public ResponseMessage handleAndSendToDefaultDestination() { + return new ResponseMessage(PAYLOAD); + } + + public ResponseMessage handleAndSendToUser() { + return ResponseMessage.destination("/queue/dest1").toUser(USERNAME).body(PAYLOAD); + } + + public ResponseMessage handleAndSendToUserMultiple() { + return ResponseMessage.destinations("/queue/dest1", "/queue/dest2").toUser(USERNAME).body(PAYLOAD); + } + + public ResponseMessage handleAndSendToCurrentUser() { + return ResponseMessage.destination("/queue/dest1").toCurrentUser().body(PAYLOAD); + } + + public ResponseMessage handleAndSendToCurrentUserSingleSession() { + return ResponseMessage.destination("/queue/dest1").toCurrentUserNoBroadcast().body(PAYLOAD); + } +} diff --git a/spring-messaging/src/test/java/org/springframework/messaging/support/ResponseMessageTest.java b/spring-messaging/src/test/java/org/springframework/messaging/support/ResponseMessageTest.java new file mode 100644 index 000000000000..9413572bea4d --- /dev/null +++ b/spring-messaging/src/test/java/org/springframework/messaging/support/ResponseMessageTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.messaging.support; + +import java.util.Arrays; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test fixture for {@link ResponseMessage} + * + * @author Sergi Almar + */ +public class ResponseMessageTest { + + private static final String PAYLOAD = "payload"; + + private static final String USERNAME = "sergi"; + + @Test + public void testBuilderDestination() { + ResponseMessage responseMessage = ResponseMessage.destination("/topic/dest1").body(PAYLOAD); + + assertEquals(PAYLOAD, responseMessage.getBody()); + assertTrue(Arrays.equals(new String[] {"/topic/dest1"}, responseMessage.getDestinations())); + assertNull(responseMessage.getUser()); + assertTrue(responseMessage.isBroadcast()); + assertFalse(responseMessage.isToCurrentUser()); + } + + @Test + public void testBuilderDestinationToUser() { + ResponseMessage responseMessage = ResponseMessage.destination("/queue/dest1").toUser(USERNAME).body(PAYLOAD); + + assertEquals(PAYLOAD, responseMessage.getBody()); + assertEquals(USERNAME, responseMessage.getUser()); + assertTrue(Arrays.equals(new String[] {"/queue/dest1"}, responseMessage.getDestinations())); + assertEquals(USERNAME, responseMessage.getUser()); + assertTrue(responseMessage.isBroadcast()); + assertFalse(responseMessage.isToCurrentUser()); + } + + + @Test + public void testBuilderDestinationToUserMultiple() { + ResponseMessage responseMessage = ResponseMessage.destinations("/queue/dest1", "/queue/dest2").toUser(USERNAME).body(PAYLOAD); + + assertEquals(PAYLOAD, responseMessage.getBody()); + assertEquals(USERNAME, responseMessage.getUser()); + assertTrue(Arrays.equals(new String[] {"/queue/dest1", "/queue/dest2"}, responseMessage.getDestinations())); + assertEquals(USERNAME, responseMessage.getUser()); + assertTrue(responseMessage.isBroadcast()); + assertFalse(responseMessage.isToCurrentUser()); + } + + @Test + public void testBuilderDestinationToCurrentUser() { + ResponseMessage responseMessage = ResponseMessage.destination("/queue/dest1").toCurrentUser().body(PAYLOAD); + + assertEquals(PAYLOAD, responseMessage.getBody()); + assertTrue(Arrays.equals(new String[] {"/queue/dest1"}, responseMessage.getDestinations())); + assertEquals(true, responseMessage.isBroadcast()); + assertNull(responseMessage.getUser()); + assertTrue(responseMessage.isToCurrentUser()); + } + + @Test + public void testBuilderDestinationToCurrentUserNoBroadcast() { + ResponseMessage responseMessage = ResponseMessage.destination("/queue/dest1").toCurrentUserNoBroadcast().body(PAYLOAD); + + assertEquals(PAYLOAD, responseMessage.getBody()); + assertTrue(Arrays.equals(new String[] {"/queue/dest1"}, responseMessage.getDestinations())); + assertNull(responseMessage.getUser()); + assertFalse(responseMessage.isBroadcast()); + assertTrue(responseMessage.isToCurrentUser()); + } + +} +