diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
index affd3fd475a..64363a1fe34 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -32,21 +32,18 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
- * A default implementation of {@link UserDestinationResolver} that relies
- * on the {@link org.springframework.messaging.simp.user.UserSessionRegistry}
- * provided to the constructor to find the sessionIds associated with a user
- * and then uses the sessionId to make the target destination unique.
+ * A default implementation of {@code UserDestinationResolver} that relies
+ * on a {@link org.springframework.messaging.simp.user.UserSessionRegistry} to
+ * find active sessions for a user.
*
- *
When a user attempts to subscribe to "/user/queue/position-updates", the
- * "/user" prefix is removed and a unique suffix added, resulting in something
- * like "/queue/position-updates-useri9oqdfzo" where the suffix is based on the
- * user's session and ensures it does not collide with any other users attempting
- * to subscribe to "/user/queue/position-updates".
+ *
When a user attempts to subscribe, e.g. to "/user/queue/position-updates",
+ * the "/user" prefix is removed and a unique suffix added based on the session
+ * id, e.g. "/queue/position-updates-useri9oqdfzo" to ensure different users can
+ * subscribe to the same logical destination without colliding.
*
- *
When a message is sent to a user with a destination such as
- * "/user/{username}/queue/position-updates", the "/user/{username}" prefix is
- * removed and the suffix added, resulting in something like
- * "/queue/position-updates-useri9oqdfzo".
+ *
When sending to a user, e.g. "/user/{username}/queue/position-updates", the
+ * "/user/{username}" prefix is removed and a suffix based on active session id's
+ * is added, e.g. "/queue/position-updates-useri9oqdfzo".
*
* @author Rossen Stoyanchev
* @author Brian Clozel
@@ -57,40 +54,19 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
private static final Log logger = LogFactory.getLog(DefaultUserDestinationResolver.class);
- private final UserSessionRegistry userSessionRegistry;
+ private final UserSessionRegistry sessionRegistry;
- private String destinationPrefix = "/user/";
+ private String prefix = "/user/";
/**
* Create an instance that will access user session id information through
* the provided registry.
- * @param userSessionRegistry the registry, never {@code null}
+ * @param sessionRegistry the registry, never {@code null}
*/
- public DefaultUserDestinationResolver(UserSessionRegistry userSessionRegistry) {
- Assert.notNull(userSessionRegistry, "'userSessionRegistry' must not be null");
- this.userSessionRegistry = userSessionRegistry;
- }
-
-
- /**
- * The prefix used to identify user destinations. Any destinations that do not
- * start with the given prefix are not be resolved.
- *
The default value is "/user/".
- * @param prefix the prefix to use
- */
- public void setUserDestinationPrefix(String prefix) {
- Assert.hasText(prefix, "prefix must not be empty");
- this.destinationPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
- }
-
- /**
- * Return the prefix used to identify user destinations. Any destinations that do not
- * start with the given prefix are not be resolved.
- *
By default "/user/queue/".
- */
- public String getDestinationPrefix() {
- return this.destinationPrefix;
+ public DefaultUserDestinationResolver(UserSessionRegistry sessionRegistry) {
+ Assert.notNull(sessionRegistry, "'sessionRegistry' must not be null");
+ this.sessionRegistry = sessionRegistry;
}
@@ -98,76 +74,91 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
* Return the configured {@link UserSessionRegistry}.
*/
public UserSessionRegistry getUserSessionRegistry() {
- return this.userSessionRegistry;
+ return this.sessionRegistry;
}
+ /**
+ * The prefix used to identify user destinations. Any destinations that do not
+ * start with the given prefix are not be resolved.
+ *
The default prefix is "/user/".
+ * @param prefix the prefix to use
+ */
+ public void setUserDestinationPrefix(String prefix) {
+ Assert.hasText(prefix, "prefix must not be empty");
+ this.prefix = prefix.endsWith("/") ? prefix : prefix + "/";
+ }
+
+ /**
+ * Return the configured prefix for user destinations.
+ */
+ public String getDestinationPrefix() {
+ return this.prefix;
+ }
+
+
@Override
public UserDestinationResult resolveDestination(Message> message) {
- String destination = SimpMessageHeaderAccessor.getDestination(message.getHeaders());
- DestinationInfo info = parseUserDestination(message);
- if (info == null) {
+ String sourceDestination = SimpMessageHeaderAccessor.getDestination(message.getHeaders());
+ ParseResult parseResult = parse(message);
+ if (parseResult == null) {
return null;
}
- Set resolved = new HashSet();
- for (String sessionId : info.getSessionIds()) {
- String targetDestination = getTargetDestination(
- destination, info.getDestinationWithoutPrefix(), sessionId, info.getUser());
+ String user = parseResult.getUser();
+ Set targetSet = new HashSet();
+ for (String sessionId : parseResult.getSessionIds()) {
+ String actualDestination = parseResult.getActualDestination();
+ String targetDestination = getTargetDestination(sourceDestination, actualDestination, sessionId, user);
if (targetDestination != null) {
- resolved.add(targetDestination);
+ targetSet.add(targetDestination);
}
}
- return new UserDestinationResult(destination, resolved, info.getSubscribeDestination(), info.getUser());
+ String subscribeDestination = parseResult.getSubscribeDestination();
+ return new UserDestinationResult(sourceDestination, targetSet, subscribeDestination, user);
}
- private DestinationInfo parseUserDestination(Message> message) {
+ private ParseResult parse(Message> message) {
MessageHeaders headers = message.getHeaders();
- SimpMessageType messageType = SimpMessageHeaderAccessor.getMessageType(headers);
String destination = SimpMessageHeaderAccessor.getDestination(headers);
- Principal principal = SimpMessageHeaderAccessor.getUser(headers);
- String sessionId = SimpMessageHeaderAccessor.getSessionId(headers);
-
- String destinationWithoutPrefix;
- String subscribeDestination;
- String user;
- Set sessionIds;
-
- if (destination == null || !checkDestination(destination, this.destinationPrefix)) {
+ if (destination == null || !checkDestination(destination, this.prefix)) {
return null;
}
-
+ SimpMessageType messageType = SimpMessageHeaderAccessor.getMessageType(headers);
+ Principal principal = SimpMessageHeaderAccessor.getUser(headers);
+ String sessionId = SimpMessageHeaderAccessor.getSessionId(headers);
if (SimpMessageType.SUBSCRIBE.equals(messageType) || SimpMessageType.UNSUBSCRIBE.equals(messageType)) {
if (sessionId == null) {
logger.error("No session id. Ignoring " + message);
return null;
}
- destinationWithoutPrefix = destination.substring(this.destinationPrefix.length()-1);
- subscribeDestination = destination;
- user = (principal != null ? principal.getName() : null);
- sessionIds = Collections.singleton(sessionId);
+ int prefixEnd = this.prefix.length() - 1;
+ String actualDestination = destination.substring(prefixEnd);
+ String user = (principal != null ? principal.getName() : null);
+ return new ParseResult(actualDestination, destination, Collections.singleton(sessionId), user);
}
else if (SimpMessageType.MESSAGE.equals(messageType)) {
- int startIndex = this.destinationPrefix.length();
- int endIndex = destination.indexOf('/', startIndex);
- Assert.isTrue(endIndex > 0, "Expected destination pattern \"/user/{userId}/**\"");
- destinationWithoutPrefix = destination.substring(endIndex);
- subscribeDestination = this.destinationPrefix.substring(0, startIndex-1) + destinationWithoutPrefix;
- user = destination.substring(startIndex, endIndex);
+ int prefixEnd = this.prefix.length();
+ int userEnd = destination.indexOf('/', prefixEnd);
+ Assert.isTrue(userEnd > 0, "Expected destination pattern \"/user/{userId}/**\"");
+ String actualDestination = destination.substring(userEnd);
+ String subscribeDestination = this.prefix.substring(0, prefixEnd - 1) + actualDestination;
+ String user = destination.substring(prefixEnd, userEnd);
user = StringUtils.replace(user, "%2F", "/");
+ Set sessionIds;
if (user.equals(sessionId)) {
user = null;
sessionIds = Collections.singleton(sessionId);
}
- else if (this.userSessionRegistry.getSessionIds(user).contains(sessionId)) {
+ else if (this.sessionRegistry.getSessionIds(user).contains(sessionId)) {
sessionIds = Collections.singleton(sessionId);
}
else {
- sessionIds = this.userSessionRegistry.getSessionIds(user);
+ sessionIds = this.sessionRegistry.getSessionIds(user);
}
+ return new ParseResult(actualDestination, subscribeDestination, sessionIds, user);
}
else {
return null;
}
- return new DestinationInfo(destinationWithoutPrefix, subscribeDestination, user, sessionIds);
}
protected boolean checkDestination(String destination, String requiredPrefix) {
@@ -175,66 +166,62 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
}
/**
- * This methods determines the translated destination to use based on the source
- * destination, the source destination with the user prefix removed, a session
- * id, and the user for the session (if known).
- * @param sourceDestination the source destination of the input message
- * @param sourceDestinationWithoutPrefix the source destination without the user prefix
- * @param sessionId the id of the session for the target message
- * @param user the user associated with the session, or {@code null}
+ * This method determines how to translate the source "user" destination to an
+ * actual target destination for the given active user session.
+ * @param sourceDestination the source destination from the input message.
+ * @param actualDestination a subset of the destination without any user prefix.
+ * @param sessionId the id of an active user session, never {@code null}.
+ * @param user the target user, possibly {@code null}, e.g if not authenticated.
* @return a target destination, or {@code null} if none
*/
- protected String getTargetDestination(String sourceDestination,
- String sourceDestinationWithoutPrefix, String sessionId, String user) {
+ protected String getTargetDestination(String sourceDestination, String actualDestination,
+ String sessionId, String user) {
- return sourceDestinationWithoutPrefix + "-user" + sessionId;
+ return actualDestination + "-user" + sessionId;
}
@Override
public String toString() {
- return "DefaultUserDestinationResolver[prefix=" + this.destinationPrefix + "]";
+ return "DefaultUserDestinationResolver[prefix=" + this.prefix + "]";
}
- private static class DestinationInfo {
+ /**
+ * A temporary placeholder for a parsed source "user" destination.
+ */
+ private static class ParseResult {
- private final String destinationWithoutPrefix;
+ private final String actualDestination;
private final String subscribeDestination;
- private final String user;
-
private final Set sessionIds;
- public DestinationInfo(String destinationWithoutPrefix, String subscribeDestination, String user,
- Set sessionIds) {
+ private final String user;
- this.user = user;
- this.destinationWithoutPrefix = destinationWithoutPrefix;
- this.subscribeDestination = subscribeDestination;
+
+ public ParseResult(String actualDest, String subscribeDest, Set sessionIds, String user) {
+ this.actualDestination = actualDest;
+ this.subscribeDestination = subscribeDest;
this.sessionIds = sessionIds;
+ this.user = user;
}
- public String getDestinationWithoutPrefix() {
- return this.destinationWithoutPrefix;
+
+ public String getActualDestination() {
+ return this.actualDestination;
}
public String getSubscribeDestination() {
return this.subscribeDestination;
}
- public String getUser() {
- return this.user;
- }
-
public Set getSessionIds() {
return this.sessionIds;
}
- @Override
- public String toString() {
- return "DestinationInfo[destination=" + this.destinationWithoutPrefix + ", subscribeDestination=" +
- this.subscribeDestination + ", user=" + this.user + ", sessionIds=" + this.sessionIds + "]";
+ public String getUser() {
+ return this.user;
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistry.java
index f14136ce011..313cbecefff 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistry.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -41,7 +41,7 @@ public class DefaultUserSessionRegistry implements UserSessionRegistry {
@Override
public Set getSessionIds(String user) {
Set set = this.userSessionIds.get(user);
- return (set != null) ? set : Collections.emptySet();
+ return (set != null ? set : Collections.emptySet());
}
@Override
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DestinationUserNameProvider.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DestinationUserNameProvider.java
index 8c0ac7d191b..2ce6d8c808a 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DestinationUserNameProvider.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DestinationUserNameProvider.java
@@ -1,19 +1,33 @@
+/*
+ * Copyright 2002-2015 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.user;
/**
- * An interface to be implemented in addition to {@link java.security.Principal}
- * when {@link java.security.Principal#getName()} is not globally unique enough
- * for use in user destinations. For more on user destination see
- * {@link org.springframework.messaging.simp.user.UserDestinationResolver}.
+ * A {@link java.security.Principal} can also implement this contract when
+ * {@link java.security.Principal#getName() getName()} isn't globally unique
+ * and therefore not suited for use with "user" destinations.
*
* @author Rossen Stoyanchev
* @since 4.0.1
+ * @see org.springframework.messaging.simp.user.UserDestinationResolver
*/
public interface DestinationUserNameProvider {
-
/**
- * Return the (globally unique) user name to use with user destinations.
+ * Return a globally unique user name for use with "user" destinations.
*/
String getDestinationUserName();
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java
index 56b810bdf6f..2223af3dba0 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -35,11 +35,11 @@ import org.springframework.messaging.support.MessageHeaderInitializer;
import org.springframework.util.Assert;
/**
- * Provides support for messages sent to "user" destinations, translating the
- * destination to one or more user-specific destination(s) and then sending message(s)
- * with the updated target destination using the provided messaging template.
- *
- * See {@link UserDestinationResolver} for more details and examples.
+ * {@code MessageHandler} with support for "user" destinations.
+ *
+ *
Listens for messages with "user" destinations, translates their destination
+ * to actual target destinations unique to the active session(s) of a user, and
+ * then sends the resolved messages to the broker channel to be delivered.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -53,9 +53,9 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
private final SubscribableChannel brokerChannel;
- private final MessageSendingOperations brokerMessagingTemplate;
+ private final MessageSendingOperations messagingTemplate;
- private final UserDestinationResolver userDestinationResolver;
+ private final UserDestinationResolver destinationResolver;
private MessageHeaderInitializer headerInitializer;
@@ -65,54 +65,53 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
/**
- * Create an instance of the handler with the given messaging template and a
- * user destination resolver.
- * @param clientInChannel the channel for receiving messages from clients (e.g. WebSocket clients)
- * @param brokerChannel the channel for sending messages with translated user destinations
- * @param userDestinationResolver the resolver to use to find queue suffixes for a user
+ * Create an instance with the given client and broker channels subscribing
+ * to handle messages from each and then sending any resolved messages to the
+ * broker channel.
+ * @param clientInboundChannel messages received from clients.
+ * @param brokerChannel messages sent to the broker.
+ * @param resolver the resolver for "user" destinations.
*/
- public UserDestinationMessageHandler(SubscribableChannel clientInChannel,
- SubscribableChannel brokerChannel, UserDestinationResolver userDestinationResolver) {
+ public UserDestinationMessageHandler(SubscribableChannel clientInboundChannel,
+ SubscribableChannel brokerChannel, UserDestinationResolver resolver) {
- Assert.notNull(clientInChannel, "'clientInChannel' must not be null");
+ Assert.notNull(clientInboundChannel, "'clientInChannel' must not be null");
Assert.notNull(brokerChannel, "'brokerChannel' must not be null");
- Assert.notNull(userDestinationResolver, "DestinationResolver must not be null");
+ Assert.notNull(resolver, "resolver must not be null");
- this.clientInboundChannel = clientInChannel;
+ this.clientInboundChannel = clientInboundChannel;
this.brokerChannel = brokerChannel;
- this.brokerMessagingTemplate = new SimpMessagingTemplate(brokerChannel);
- this.userDestinationResolver = userDestinationResolver;
+ this.messagingTemplate = new SimpMessagingTemplate(brokerChannel);
+ this.destinationResolver = resolver;
}
- /**
- * Return the configured messaging template for sending messages with
- * translated destinations.
- */
- public MessageSendingOperations getBrokerMessagingTemplate() {
- return this.brokerMessagingTemplate;
- }
-
/**
* Return the configured {@link UserDestinationResolver}.
*/
public UserDestinationResolver getUserDestinationResolver() {
- return this.userDestinationResolver;
+ return this.destinationResolver;
}
/**
- * Configure a {@link MessageHeaderInitializer} to pass on to
- * {@link org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler}s
- * that send messages from controller return values.
- *
- * By default this property is not set.
+ * Return the messaging template used to send resolved messages to the
+ * broker channel.
+ */
+ public MessageSendingOperations getBrokerMessagingTemplate() {
+ return this.messagingTemplate;
+ }
+
+ /**
+ * Configure a custom {@link MessageHeaderInitializer} to initialize the
+ * headers of resolved target messages.
+ * By default this is not set.
*/
public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) {
this.headerInitializer = headerInitializer;
}
/**
- * @return the configured header initializer.
+ * Return the configured header initializer.
*/
public MessageHeaderInitializer getHeaderInitializer() {
return this.headerInitializer;
@@ -165,7 +164,7 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
@Override
public void handleMessage(Message> message) throws MessagingException {
- UserDestinationResult result = this.userDestinationResolver.resolveDestination(message);
+ UserDestinationResult result = this.destinationResolver.resolveDestination(message);
if (result == null) {
return;
}
@@ -177,17 +176,17 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
return;
}
if (SimpMessageType.MESSAGE.equals(SimpMessageHeaderAccessor.getMessageType(message.getHeaders()))) {
- SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message);
- initHeaders(headerAccessor);
+ SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
+ initHeaders(accessor);
String header = SimpMessageHeaderAccessor.ORIGINAL_DESTINATION;
- headerAccessor.setNativeHeader(header, result.getSubscribeDestination());
- message = MessageBuilder.createMessage(message.getPayload(), headerAccessor.getMessageHeaders());
+ accessor.setNativeHeader(header, result.getSubscribeDestination());
+ message = MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders());
}
if (logger.isDebugEnabled()) {
logger.debug("Translated " + result.getSourceDestination() + " -> " + destinations);
}
for (String destination : destinations) {
- this.brokerMessagingTemplate.send(destination, message);
+ this.messagingTemplate.send(destination, message);
}
}
@@ -199,7 +198,7 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
@Override
public String toString() {
- return "UserDestinationMessageHandler[" + this.userDestinationResolver + "]";
+ return "UserDestinationMessageHandler[" + this.destinationResolver + "]";
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResolver.java
index ede147bfa34..2354c05f314 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -19,17 +19,14 @@ package org.springframework.messaging.simp.user;
import org.springframework.messaging.Message;
/**
- * A strategy for resolving a "user" destination and translating it to one or more
- * actual destinations unique to the user's active session(s).
- *
- * For messages sent to a user, the destination must contain the name of the target
- * user, The name, extracted from the destination, is used to look up the active
- * user session(s), and then translate the destination accordingly.
- *
- * For SUBSCRIBE and UNSUBSCRIBE messages, the user is the user associated with
- * the message. In other words the destination does not contain the user name.
- *
- * See the documentation on implementations for specific examples.
+ * A strategy for resolving a "user" destination by translating it to one or more
+ * actual destinations one per active user session. When sending a message to a
+ * user destination, the destination must contain the user name so it may be
+ * extracted and used to look up the user sessions. When subscribing to a user
+ * destination, the destination does not have to contain the user's own name.
+ * We simply use the current session.
+ *
+ *
See implementation classes and the documentation for example destinations.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -40,18 +37,11 @@ import org.springframework.messaging.Message;
public interface UserDestinationResolver {
/**
- * Resolve the destination of the message to a set of actual target destinations.
- *
- * If the message is SUBSCRIBE/UNSUBSCRIBE, the returned set will contain a
- * single translated target destination.
- *
- * If the message represents data being sent to a user, the returned set may
- * contain multiple target destinations, one for each active user session.
- *
- * @param message the message with a user destination to be resolved
- *
- * @return the result of the resolution, or {@code null} if the resolution
- * fails (e.g. not a user destination, or no user info available, etc)
+ * Resolve the given message with a user destination to one or more messages
+ * with actual destinations, one for each active user session.
+ * @param message the message to try to resolve
+ * @return 0 or more target messages (one for each active session), or
+ * {@code null} if the source message does not contain a user destination.
*/
UserDestinationResult resolveDestination(Message> message);
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResult.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResult.java
index 16c7dd16fc7..4d7765a945a 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResult.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationResult.java
@@ -21,12 +21,12 @@ import java.util.Set;
import org.springframework.util.Assert;
/**
- * A simple container for the result of parsing and translating a "user" destination
- * in some source message into a set of actual target destinations by calling
- * {@link org.springframework.messaging.simp.user.UserDestinationResolver}.
+ * Contains the result from parsing a "user" destination from a source message
+ * and translating it to target destinations (one per active user session).
*
* @author Rossen Stoyanchev
* @since 4.0.2
+ * @see org.springframework.messaging.simp.user.UserDestinationResolver
*/
public class UserDestinationResult {
@@ -54,39 +54,40 @@ public class UserDestinationResult {
/**
- * The "user" destination as found in the headers of the source message.
- *
- * @return a destination, never {@code null}
+ * The "user" destination from the source message. This may look like
+ * "/user/queue/position-updates" when subscribing or
+ * "/user/{username}/queue/position-updates" when sending a message.
+ * @return the "user" destination, never {@code null}.
*/
public String getSourceDestination() {
return this.sourceDestination;
}
/**
- * The result of parsing the source destination and translating it into a set
- * of actual target destinations to use.
- *
- * @return a set of destination values, possibly an empty set
+ * The target destinations that the source destination was translated to,
+ * one per active user session, e.g. "/queue/position-updates-useri9oqdfzo".
+ * @return the target destinations, never {@code null} but possibly an empty
+ * set if there are no active sessions for the user.
*/
public Set getTargetDestinations() {
return this.targetDestinations;
}
/**
- * The canonical form of the user destination as would be required to subscribe.
- * This may be useful to ensure that messages received by clients contain the
- * original destination they used to subscribe.
- *
- * @return a destination, never {@code null}
+ * The user destination in the form expected when a client subscribes, e.g.
+ * "/user/queue/position-updates".
+ * @return the subscribe form of the "user" destination, never {@code null}.
*/
public String getSubscribeDestination() {
return this.subscribeDestination;
}
/**
- * The user associated with the user destination.
- *
- * @return the user name, never {@code null}
+ * The user for this user destination.
+ * @return the user name or {@code null} if we have a session id only such as
+ * when the user is not authenticated; in such cases it is possible to use
+ * sessionId in place of a user name thus removing the need for a user-to-session
+ * lookup via {@link org.springframework.messaging.simp.user.UserSessionRegistry}.
*/
public String getUser() {
return this.user;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistry.java
index f23da3e530c..05b780b816c 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistry.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistry.java
@@ -1,12 +1,26 @@
+/*
+ * Copyright 2002-2015 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.user;
import java.util.Set;
/**
- * A registry for looking up active session id's by user.
- *
- * Used in support of resolving unique session-specific user destinations.
- * See {@link DefaultUserDestinationResolver} for more details.
+ * A registry for looking up active user sessions. For use when resolving user
+ * destinations.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -14,24 +28,22 @@ import java.util.Set;
*/
public interface UserSessionRegistry {
-
-
/**
- * Return the active session id's for the given user.
+ * Return the active session id's for the user.
* @param user the user
- * @return a set with 0 or more session id's
+ * @return a set with 0 or more session id's, never {@code null}.
*/
Set getSessionIds(String user);
/**
- * Register an active session id for the given user.
+ * Register an active session id for a user.
* @param user the user
* @param sessionId the session id
*/
void registerSessionId(String user, String sessionId);
/**
- * Unregister the session id for a user.
+ * Unregister an active session id for a user.
* @param user the user
* @param sessionId the session id
*/
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
index 112c56e36fd..455bfd4d06b 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -16,6 +16,8 @@
package org.springframework.messaging.simp.user;
+import static org.junit.Assert.*;
+
import org.junit.Before;
import org.junit.Test;
@@ -26,8 +28,6 @@ import org.springframework.messaging.simp.TestPrincipal;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.StringUtils;
-import static org.junit.Assert.*;
-
/**
* Unit tests for
* {@link org.springframework.messaging.simp.user.DefaultUserDestinationResolver}.
@@ -57,7 +57,7 @@ public class DefaultUserDestinationResolverTests {
@Test
public void handleSubscribe() {
String sourceDestination = "/user/queue/foo";
- Message> message = createMessage(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, sourceDestination);
+ Message> message = createWith(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
@@ -75,7 +75,7 @@ public class DefaultUserDestinationResolverTests {
this.registry.registerSessionId("joe", "456");
this.registry.registerSessionId("joe", "789");
- Message> message = createMessage(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, "/user/queue/foo");
+ Message> message = createWith(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, "/user/queue/foo");
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(1, actual.getTargetDestinations().size());
@@ -85,7 +85,7 @@ public class DefaultUserDestinationResolverTests {
@Test
public void handleSubscribeNoUser() {
String sourceDestination = "/user/queue/foo";
- Message> message = createMessage(SimpMessageType.SUBSCRIBE, null, SESSION_ID, sourceDestination);
+ Message> message = createWith(SimpMessageType.SUBSCRIBE, null, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
@@ -97,7 +97,7 @@ public class DefaultUserDestinationResolverTests {
@Test
public void handleUnsubscribe() {
- Message> message = createMessage(SimpMessageType.UNSUBSCRIBE, this.user, SESSION_ID, "/user/queue/foo");
+ Message> message = createWith(SimpMessageType.UNSUBSCRIBE, this.user, SESSION_ID, "/user/queue/foo");
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(1, actual.getTargetDestinations().size());
@@ -107,7 +107,7 @@ public class DefaultUserDestinationResolverTests {
@Test
public void handleMessage() {
String sourceDestination = "/user/joe/queue/foo";
- Message> message = createMessage(SimpMessageType.MESSAGE, this.user, SESSION_ID, sourceDestination);
+ Message> message = createWith(SimpMessageType.MESSAGE, this.user, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
@@ -126,7 +126,7 @@ public class DefaultUserDestinationResolverTests {
String sourceDestination = "/user/"+OTHER_USER_NAME+"/queue/foo";
TestPrincipal otherUser = new TestPrincipal(OTHER_USER_NAME);
this.registry.registerSessionId(otherUser.getName(), OTHER_SESSION_ID);
- Message> message = createMessage(SimpMessageType.MESSAGE, this.user, SESSION_ID, sourceDestination);
+ Message> message = createWith(SimpMessageType.MESSAGE, this.user, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
@@ -142,7 +142,7 @@ public class DefaultUserDestinationResolverTests {
String userName = "http://joe.openid.example.org/";
this.registry.registerSessionId(userName, "openid123");
String destination = "/user/" + StringUtils.replace(userName, "/", "%2F") + "/queue/foo";
- Message> message = createMessage(SimpMessageType.MESSAGE, this.user, null, destination);
+ Message> message = createWith(SimpMessageType.MESSAGE, this.user, null, destination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(1, actual.getTargetDestinations().size());
@@ -152,7 +152,7 @@ public class DefaultUserDestinationResolverTests {
@Test
public void handleMessageWithNoUser() {
String sourceDestination = "/user/" + SESSION_ID + "/queue/foo";
- Message> message = createMessage(SimpMessageType.MESSAGE, null, SESSION_ID, sourceDestination);
+ Message> message = createWith(SimpMessageType.MESSAGE, null, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
@@ -166,29 +166,29 @@ public class DefaultUserDestinationResolverTests {
public void ignoreMessage() {
// no destination
- Message> message = createMessage(SimpMessageType.MESSAGE, this.user, SESSION_ID, null);
+ Message> message = createWith(SimpMessageType.MESSAGE, this.user, SESSION_ID, null);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertNull(actual);
// not a user destination
- message = createMessage(SimpMessageType.MESSAGE, this.user, SESSION_ID, "/queue/foo");
+ message = createWith(SimpMessageType.MESSAGE, this.user, SESSION_ID, "/queue/foo");
actual = this.resolver.resolveDestination(message);
assertNull(actual);
// subscribe + not a user destination
- message = createMessage(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, "/queue/foo");
+ message = createWith(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, "/queue/foo");
actual = this.resolver.resolveDestination(message);
assertNull(actual);
// no match on message type
- message = createMessage(SimpMessageType.CONNECT, this.user, SESSION_ID, "user/joe/queue/foo");
+ message = createWith(SimpMessageType.CONNECT, this.user, SESSION_ID, "user/joe/queue/foo");
actual = this.resolver.resolveDestination(message);
assertNull(actual);
}
- private Message> createMessage(SimpMessageType messageType, TestPrincipal user, String sessionId, String destination) {
- SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(messageType);
+ private Message> createWith(SimpMessageType type, TestPrincipal user, String sessionId, String destination) {
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type);
if (destination != null) {
headers.setDestination(destination);
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistryTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistryTests.java
index 049d2fd82f3..8d93deec6ab 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistryTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserSessionRegistryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -16,6 +16,8 @@
package org.springframework.messaging.simp.user;
+import static org.junit.Assert.*;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -23,10 +25,9 @@ import java.util.List;
import org.junit.Test;
-import static org.junit.Assert.*;
-
/**
- * Test fixture for {@link org.springframework.messaging.simp.user.DefaultUserSessionRegistry}
+ * Test fixture for
+ * {@link org.springframework.messaging.simp.user.DefaultUserSessionRegistry}
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -57,10 +58,9 @@ public class DefaultUserSessionRegistryTests {
}
assertEquals(new LinkedHashSet<>(sessionIds), resolver.getSessionIds(user));
- assertEquals(Collections.emptySet(), resolver.getSessionIds("jane"));
+ assertEquals(Collections.emptySet(), resolver.getSessionIds("jane"));
}
-
@Test
public void removeSessionIds() {
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java
index 43d595c16a5..b112c51f3b5 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -16,6 +16,10 @@
package org.springframework.messaging.simp.user;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.springframework.messaging.simp.SimpMessageHeaderAccessor.ORIGINAL_DESTINATION;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -31,30 +35,29 @@ import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.TestPrincipal;
import org.springframework.messaging.support.MessageBuilder;
-import static org.junit.Assert.*;
-import static org.mockito.BDDMockito.*;
-
/**
- * Unit tests for {@link org.springframework.messaging.simp.user.UserDestinationMessageHandler}.
+ * Unit tests for
+ * {@link org.springframework.messaging.simp.user.UserDestinationMessageHandler}.
*/
public class UserDestinationMessageHandlerTests {
- public static final String SESSION_ID = "123";
- private UserDestinationMessageHandler messageHandler;
+ private static final String SESSION_ID = "123";
+ private UserDestinationMessageHandler handler;
+
+ private UserSessionRegistry registry;
+
@Mock
private SubscribableChannel brokerChannel;
- private UserSessionRegistry registry;
-
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.registry = new DefaultUserSessionRegistry();
- DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(this.registry);
- this.messageHandler = new UserDestinationMessageHandler(new StubMessageChannel(), this.brokerChannel, resolver);
+ UserDestinationResolver resolver = new DefaultUserDestinationResolver(this.registry);
+ this.handler = new UserDestinationMessageHandler(new StubMessageChannel(), this.brokerChannel, resolver);
}
@@ -62,24 +65,26 @@ public class UserDestinationMessageHandlerTests {
@SuppressWarnings("rawtypes")
public void handleSubscribe() {
given(this.brokerChannel.send(Mockito.any(Message.class))).willReturn(true);
- this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, "joe", SESSION_ID, "/user/queue/foo"));
+ this.handler.handleMessage(createWith(SimpMessageType.SUBSCRIBE, "joe", SESSION_ID, "/user/queue/foo"));
ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.brokerChannel).send(captor.capture());
- assertEquals("/queue/foo-user123", SimpMessageHeaderAccessor.getDestination(captor.getValue().getHeaders()));
+ Message message = captor.getValue();
+ assertEquals("/queue/foo-user123", SimpMessageHeaderAccessor.getDestination(message.getHeaders()));
}
@Test
@SuppressWarnings("rawtypes")
public void handleUnsubscribe() {
given(this.brokerChannel.send(Mockito.any(Message.class))).willReturn(true);
- this.messageHandler.handleMessage(createMessage(SimpMessageType.UNSUBSCRIBE, "joe", "123", "/user/queue/foo"));
+ this.handler.handleMessage(createWith(SimpMessageType.UNSUBSCRIBE, "joe", "123", "/user/queue/foo"));
ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.brokerChannel).send(captor.capture());
- assertEquals("/queue/foo-user123", SimpMessageHeaderAccessor.getDestination(captor.getValue().getHeaders()));
+ Message message = captor.getValue();
+ assertEquals("/queue/foo-user123", SimpMessageHeaderAccessor.getDestination(message.getHeaders()));
}
@Test
@@ -87,14 +92,14 @@ public class UserDestinationMessageHandlerTests {
public void handleMessage() {
this.registry.registerSessionId("joe", "123");
given(this.brokerChannel.send(Mockito.any(Message.class))).willReturn(true);
- this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", "123", "/user/joe/queue/foo"));
+ this.handler.handleMessage(createWith(SimpMessageType.MESSAGE, "joe", "123", "/user/joe/queue/foo"));
ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.brokerChannel).send(captor.capture());
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(captor.getValue());
assertEquals("/queue/foo-user123", accessor.getDestination());
- assertEquals("/user/queue/foo", accessor.getFirstNativeHeader(SimpMessageHeaderAccessor.ORIGINAL_DESTINATION));
+ assertEquals("/user/queue/foo", accessor.getFirstNativeHeader(ORIGINAL_DESTINATION));
}
@@ -102,25 +107,25 @@ public class UserDestinationMessageHandlerTests {
public void ignoreMessage() {
// no destination
- this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", "123", null));
+ this.handler.handleMessage(createWith(SimpMessageType.MESSAGE, "joe", "123", null));
Mockito.verifyZeroInteractions(this.brokerChannel);
// not a user destination
- this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", "123", "/queue/foo"));
+ this.handler.handleMessage(createWith(SimpMessageType.MESSAGE, "joe", "123", "/queue/foo"));
Mockito.verifyZeroInteractions(this.brokerChannel);
// subscribe + not a user destination
- this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, "joe", "123", "/queue/foo"));
+ this.handler.handleMessage(createWith(SimpMessageType.SUBSCRIBE, "joe", "123", "/queue/foo"));
Mockito.verifyZeroInteractions(this.brokerChannel);
// no match on message type
- this.messageHandler.handleMessage(createMessage(SimpMessageType.CONNECT, "joe", "123", "user/joe/queue/foo"));
+ this.handler.handleMessage(createWith(SimpMessageType.CONNECT, "joe", "123", "user/joe/queue/foo"));
Mockito.verifyZeroInteractions(this.brokerChannel);
}
- private Message> createMessage(SimpMessageType messageType, String user, String sessionId, String destination) {
- SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(messageType);
+ private Message> createWith(SimpMessageType type, String user, String sessionId, String destination) {
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type);
if (destination != null) {
headers.setDestination(destination);
}