@ -1,5 +1,5 @@
@@ -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;
@@ -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 .
*
* < p > 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" .
* < p > 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 .
*
* < p > 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" .
* < p > 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,117 +54,111 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@@ -57,117 +54,111 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
private static final Log logger = LogFactory . getLog ( DefaultUserDestinationResolver . class ) ;
private final UserSessionRegistry u serS essionRegistry;
private final UserSessionRegistry sessionRegistry ;
private String destinationP refix = "/user/" ;
private String p refix = "/user/" ;
/ * *
* Create an instance that will access user session id information through
* the provided registry .
* @param u serS essionRegistry the registry , never { @code null }
* @param sessionRegistry the registry , never { @code null }
* /
public DefaultUserDestinationResolver ( UserSessionRegistry u serS essionRegistry) {
Assert . notNull ( u serS essionRegistry, "'u serS essionRegistry' must not be null" ) ;
this . u serS essionRegistry = u serS essionRegistry;
public DefaultUserDestinationResolver ( UserSessionRegistry sessionRegistry ) {
Assert . notNull ( sessionRegistry , "'sessionRegistry' must not be null" ) ;
this . sessionRegistry = sessionRegistry ;
}
/ * *
* Return the configured { @link UserSessionRegistry } .
* /
public UserSessionRegistry getUserSessionRegistry ( ) {
return this . sessionRegistry ;
}
/ * *
* The prefix used to identify user destinations . Any destinations that do not
* start with the given prefix are not be resolved .
* < p > The default value is "/user/" .
* < p > 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 . destinationP refix = prefix . endsWith ( "/" ) ? prefix : prefix + "/" ;
this . p refix = 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 .
* < p > By default "/user/queue/" .
* Return the configured prefix for user destinations .
* /
public String getDestinationPrefix ( ) {
return this . destinationP refix;
return this . p refix;
}
/ * *
* Return the configured { @link UserSessionRegistry } .
* /
public UserSessionRegistry getUserSessionRegistry ( ) {
return this . userSessionRegistry ;
}
@Override
public UserDestinationResult resolveDestination ( Message < ? > message ) {
String d estination = 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 < String > resolved = new HashSet < String > ( ) ;
for ( String sessionId : info . getSessionIds ( ) ) {
String targetDestination = getTargetDestination (
destination , info . getDestinationWithoutPrefix ( ) , sessionId , info . getUser ( ) ) ;
String user = parseResult . getUser ( ) ;
Set < String > targetSet = new HashSet < String > ( ) ;
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 < String > 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 . destinationP refix. length ( ) - 1 ) ;
subscribe Destination = destination ;
user = ( principal ! = null ? principal . getName ( ) : null ) ;
sessionIds = Collections . singleton ( sessionId ) ;
int prefixEnd = this . p refix. length ( ) - 1 ;
String actual Destination = 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 . destinationP refix. length ( ) ;
int endIndex = destination . indexOf ( '/' , startIndex ) ;
Assert . isTrue ( endIndex > 0 , "Expected destination pattern \"/user/{userId}/**\"" ) ;
destinationWithoutPrefix = destination . substring ( endIndex ) ;
subscribeDestination = this . destinationP refix. substring ( 0 , startIndex - 1 ) + destinationWithoutPrefix ;
user = destination . substring ( startIndex , endIndex ) ;
int prefixEnd = this . p refix. length ( ) ;
int us erE nd = destination . indexOf ( '/' , prefixEnd ) ;
Assert . isTrue ( us erE nd > 0 , "Expected destination pattern \"/user/{userId}/**\"" ) ;
String actualDestination = destination . substring ( us erE nd) ;
String subscribeDestination = this . p refix. substring ( 0 , prefixEnd - 1 ) + actualDestination ;
String user = destination . substring ( prefixEnd , userEnd ) ;
user = StringUtils . replace ( user , "%2F" , "/" ) ;
Set < String > sessionIds ;
if ( user . equals ( sessionId ) ) {
user = null ;
sessionIds = Collections . singleton ( sessionId ) ;
}
else if ( this . u serS essionRegistry. getSessionIds ( user ) . contains ( sessionId ) ) {
else if ( this . sessionRegistry . getSessionIds ( user ) . contains ( sessionId ) ) {
sessionIds = Collections . singleton ( sessionId ) ;
}
else {
sessionIds = this . u serS essionRegistry. 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 {
@@ -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 s essionId , 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 . destinationP refix + "]" ;
return "DefaultUserDestinationResolver[prefix=" + this . p refix + "]" ;
}
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 < String > sessionIds ;
public DestinationInfo ( String destinationWithoutPrefix , String subscribeDestination , String user ,
Set < String > sessionIds ) {
private final String user ;
this . user = user ;
this . destinationWithoutPrefix = destinationWithoutPrefix ;
this . subscribeDestination = subscribeDestination ;
public ParseResult ( String actualDest , String subscribeDest , Set < String > 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 < String > 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 ;
}
}