Update support for using "." as path separator
Issue: SPR-11660
This commit is contained in:
parent
928a466b5d
commit
ab2526a586
|
|
@ -68,30 +68,35 @@ public class AntPathMatcher implements PathMatcher {
|
||||||
|
|
||||||
final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, AntPathStringMatcher>(256);
|
final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, AntPathStringMatcher>(256);
|
||||||
|
|
||||||
|
private PathSeparatorPatternCache pathSeparatorPatternCache = new PathSeparatorPatternCache(DEFAULT_PATH_SEPARATOR);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new AntPathMatcher with the default Ant path separator "/".
|
* Create a new instance with the {@link #DEFAULT_PATH_SEPARATOR}.
|
||||||
*/
|
*/
|
||||||
public AntPathMatcher() {
|
public AntPathMatcher() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new AntPathMatcher.
|
* A convenience alternative constructor to use with a custom path separator.
|
||||||
* @param pathSeparator the path separator to use
|
* @param pathSeparator the path separator to use, must not be {@code null}.
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
public AntPathMatcher(String pathSeparator) {
|
public AntPathMatcher(String pathSeparator) {
|
||||||
if(pathSeparator != null) {
|
Assert.notNull(pathSeparator, "'pathSeparator' is required");
|
||||||
this.pathSeparator = pathSeparator;
|
this.pathSeparator = pathSeparator;
|
||||||
}
|
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the path separator to use for pattern parsing.
|
* Set the path separator to use for pattern parsing.
|
||||||
* Default is "/", as in Ant.
|
* Default is "/", as in Ant.
|
||||||
*/
|
*/
|
||||||
public void setPathSeparator(String pathSeparator) {
|
public void setPathSeparator(String pathSeparator) {
|
||||||
this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
|
this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
|
||||||
|
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -447,20 +452,20 @@ public class AntPathMatcher implements PathMatcher {
|
||||||
|
|
||||||
// /hotels/* + /booking -> /hotels/booking
|
// /hotels/* + /booking -> /hotels/booking
|
||||||
// /hotels/* + booking -> /hotels/booking
|
// /hotels/* + booking -> /hotels/booking
|
||||||
if (pattern1.endsWith(this.pathSeparator + "*")) {
|
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
|
||||||
return separatorConcat(pattern1.substring(0, pattern1.length() - 2), pattern2);
|
return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /hotels/** + /booking -> /hotels/**/booking
|
// /hotels/** + /booking -> /hotels/**/booking
|
||||||
// /hotels/** + booking -> /hotels/**/booking
|
// /hotels/** + booking -> /hotels/**/booking
|
||||||
if (pattern1.endsWith(this.pathSeparator + "**")) {
|
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
|
||||||
return separatorConcat(pattern1, pattern2);
|
return concat(pattern1, pattern2);
|
||||||
}
|
}
|
||||||
|
|
||||||
int starDotPos1 = pattern1.indexOf("*.");
|
int starDotPos1 = pattern1.indexOf("*.");
|
||||||
if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
|
if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
|
||||||
// simply concatenate the two patterns
|
// simply concatenate the two patterns
|
||||||
return separatorConcat(pattern1, pattern2);
|
return concat(pattern1, pattern2);
|
||||||
}
|
}
|
||||||
String extension1 = pattern1.substring(starDotPos1 + 1);
|
String extension1 = pattern1.substring(starDotPos1 + 1);
|
||||||
int dotPos2 = pattern2.indexOf('.');
|
int dotPos2 = pattern2.indexOf('.');
|
||||||
|
|
@ -470,7 +475,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||||
return fileName2 + extension;
|
return fileName2 + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String separatorConcat(String path1, String path2) {
|
private String concat(String path1, String path2) {
|
||||||
if (path1.endsWith(this.pathSeparator) || path2.startsWith(this.pathSeparator)) {
|
if (path1.endsWith(this.pathSeparator) || path2.startsWith(this.pathSeparator)) {
|
||||||
return path1 + path2;
|
return path1 + path2;
|
||||||
}
|
}
|
||||||
|
|
@ -763,4 +768,29 @@ public class AntPathMatcher implements PathMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple cache for patterns that depend on the configured path separator.
|
||||||
|
*/
|
||||||
|
private static class PathSeparatorPatternCache {
|
||||||
|
|
||||||
|
private final String endsOnWildCard;
|
||||||
|
|
||||||
|
private final String endsOnDoubleWildCard;
|
||||||
|
|
||||||
|
|
||||||
|
private PathSeparatorPatternCache(String pathSeparator) {
|
||||||
|
this.endsOnWildCard = pathSeparator + "*";
|
||||||
|
this.endsOnDoubleWildCard = pathSeparator + "**";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEndsOnWildCard() {
|
||||||
|
return this.endsOnWildCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEndsOnDoubleWildCard() {
|
||||||
|
return this.endsOnDoubleWildCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -607,9 +607,10 @@ public class AntPathMatcherTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noExtensionHandlingWithDotSeparator() {
|
public void testExtensionMappingWithDotPathSeparator() {
|
||||||
pathMatcher.setPathSeparator(".");
|
pathMatcher.setPathSeparator(".");
|
||||||
assertEquals("/*.html.hotel.*", pathMatcher.combine("/*.html", "hotel.*"));
|
assertEquals("Extension mapping should be disabled with \".\" as path separator",
|
||||||
|
"/*.html.hotel.*", pathMatcher.combine("/*.html", "hotel.*"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -55,51 +56,38 @@ public final class DestinationPatternsMessageCondition
|
||||||
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
|
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
|
||||||
*/
|
*/
|
||||||
public DestinationPatternsMessageCondition(String... patterns) {
|
public DestinationPatternsMessageCondition(String... patterns) {
|
||||||
this(patterns, null, true);
|
this(patterns, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional constructor with a customized path matcher.
|
* Alternative constructor accepting a custom PathMatcher.
|
||||||
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
|
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
|
||||||
* @param pathMatcher the customized path matcher to use with patterns
|
* @param pathMatcher the PathMatcher to use.
|
||||||
*/
|
*/
|
||||||
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher) {
|
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher) {
|
||||||
this(asList(patterns), pathMatcher, true);
|
this(asList(patterns), pathMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private DestinationPatternsMessageCondition(Collection<String> patterns, PathMatcher pathMatcher) {
|
||||||
* Additional constructor with a customized path matcher and a flag specifying if
|
|
||||||
* the destination patterns should be prepended with "/".
|
|
||||||
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
|
|
||||||
* @param pathMatcher the customized path matcher to use with patterns
|
|
||||||
* @param prependLeadingSlash to specify whether each pattern that is not empty and does not
|
|
||||||
* start with "/" will be prepended with "/" or not
|
|
||||||
* @since 4.1
|
|
||||||
*/
|
|
||||||
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher,
|
|
||||||
boolean prependLeadingSlash) {
|
|
||||||
this(asList(patterns), pathMatcher, prependLeadingSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DestinationPatternsMessageCondition(Collection<String> patterns,
|
|
||||||
PathMatcher pathMatcher, boolean prependLeadingSlash) {
|
|
||||||
this.patterns = Collections.unmodifiableSet(initializePatterns(patterns, prependLeadingSlash));
|
|
||||||
this.pathMatcher = (pathMatcher != null) ? pathMatcher : new AntPathMatcher();
|
this.pathMatcher = (pathMatcher != null) ? pathMatcher : new AntPathMatcher();
|
||||||
|
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns, this.pathMatcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> asList(String... patterns) {
|
private static List<String> asList(String... patterns) {
|
||||||
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
|
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> initializePatterns(Collection<String> patterns,
|
private static Set<String> prependLeadingSlash(Collection<String> patterns, PathMatcher pathMatcher) {
|
||||||
boolean prependLeadingSlash) {
|
|
||||||
if (patterns == null) {
|
if (patterns == null) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
boolean slashSeparator = pathMatcher.combine("a", "a").equals("a/a");
|
||||||
Set<String> result = new LinkedHashSet<String>(patterns.size());
|
Set<String> result = new LinkedHashSet<String>(patterns.size());
|
||||||
for (String pattern : patterns) {
|
for (String pattern : patterns) {
|
||||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/") && prependLeadingSlash) {
|
if (slashSeparator) {
|
||||||
pattern = "/" + pattern;
|
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||||
|
pattern = "/" + pattern;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result.add(pattern);
|
result.add(pattern);
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +137,7 @@ public final class DestinationPatternsMessageCondition
|
||||||
else {
|
else {
|
||||||
result.add("");
|
result.add("");
|
||||||
}
|
}
|
||||||
return new DestinationPatternsMessageCondition(result, this.pathMatcher, false);
|
return new DestinationPatternsMessageCondition(result, this.pathMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -184,7 +172,7 @@ public final class DestinationPatternsMessageCondition
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(matches, this.pathMatcher.getPatternComparator(destination));
|
Collections.sort(matches, this.pathMatcher.getPatternComparator(destination));
|
||||||
return new DestinationPatternsMessageCondition(matches, this.pathMatcher, false);
|
return new DestinationPatternsMessageCondition(matches, this.pathMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -90,27 +90,27 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure one or more prefixes to match to the destinations of handled messages.
|
* When this property is configured only messages to destinations matching
|
||||||
* Messages whose destination does not start with one of the configured prefixes
|
* one of the configured prefixes are eligible for handling. When there is a
|
||||||
* are ignored. When a destination matches one of the configured prefixes, the
|
* match the prefix is removed and only the remaining part of the destination
|
||||||
* matching part is removed from destination before performing a lookup for a matching
|
* is used for method-mapping purposes.
|
||||||
* message handling method. Prefixes without a trailing slash will have one appended
|
*
|
||||||
* automatically.
|
* <p>By default no prefixes are configured in which case all messages are
|
||||||
* <p>By default the list of prefixes is empty in which case all destinations match.
|
* eligible for handling.
|
||||||
*/
|
*/
|
||||||
public void setDestinationPrefixes(Collection<String> prefixes) {
|
public void setDestinationPrefixes(Collection<String> prefixes) {
|
||||||
this.destinationPrefixes.clear();
|
this.destinationPrefixes.clear();
|
||||||
if (prefixes != null) {
|
if (prefixes != null) {
|
||||||
for (String prefix : prefixes) {
|
for (String prefix : prefixes) {
|
||||||
prefix = prefix.trim();
|
prefix = prefix.trim();
|
||||||
if (!prefix.endsWith("/")) {
|
|
||||||
prefix += "/";
|
|
||||||
}
|
|
||||||
this.destinationPrefixes.add(prefix);
|
this.destinationPrefixes.add(prefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configured destination prefixes.
|
||||||
|
*/
|
||||||
public Collection<String> getDestinationPrefixes() {
|
public Collection<String> getDestinationPrefixes() {
|
||||||
return this.destinationPrefixes;
|
return this.destinationPrefixes;
|
||||||
}
|
}
|
||||||
|
|
@ -346,11 +346,11 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||||
protected abstract String getDestination(Message<?> message);
|
protected abstract String getDestination(Message<?> message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find if the given destination matches any of the configured allowed destination
|
* Check whether the given destination (of an incoming message) matches to
|
||||||
* prefixes and if a match is found return the destination with the prefix removed.
|
* one of the configured destination prefixes and if so return the remaining
|
||||||
* <p>If no destination prefixes are configured, the destination is returned as is.
|
* portion of the destination after the matched prefix.
|
||||||
* @return the destination to use to find matching message handling methods
|
* <p>If there are no matching prefixes, return {@code null}.
|
||||||
* or {@code null} if the destination does not match
|
* <p>If there are no destination prefixes, return the destination as is.
|
||||||
*/
|
*/
|
||||||
protected String getLookupDestination(String destination) {
|
protected String getLookupDestination(String destination) {
|
||||||
if (destination == null) {
|
if (destination == null) {
|
||||||
|
|
@ -361,7 +361,7 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||||
}
|
}
|
||||||
for (String prefix : this.destinationPrefixes) {
|
for (String prefix : this.destinationPrefixes) {
|
||||||
if (destination.startsWith(prefix)) {
|
if (destination.startsWith(prefix)) {
|
||||||
return destination.substring(prefix.length() - 1);
|
return destination.substring(prefix.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,9 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
|
|
||||||
private ConversionService conversionService = new DefaultFormattingConversionService();
|
private ConversionService conversionService = new DefaultFormattingConversionService();
|
||||||
|
|
||||||
private PathMatcher pathMatcher;
|
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||||
|
|
||||||
|
private boolean slashPathSeparator = true;
|
||||||
|
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
|
|
@ -112,22 +114,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
* @param brokerTemplate a messaging template to send application messages to the broker
|
* @param brokerTemplate a messaging template to send application messages to the broker
|
||||||
*/
|
*/
|
||||||
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
|
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
|
||||||
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
|
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
|
||||||
this(clientInboundChannel, clientOutboundChannel, brokerTemplate, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of SimpAnnotationMethodMessageHandler with the given
|
|
||||||
* message channels and broker messaging template.
|
|
||||||
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
|
|
||||||
* @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients)
|
|
||||||
* @param brokerTemplate a messaging template to send application messages to the broker
|
|
||||||
* @param pathSeparator the path separator to use with the destination patterns
|
|
||||||
* @since 4.1
|
|
||||||
*/
|
|
||||||
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
|
|
||||||
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate,
|
|
||||||
String pathSeparator) {
|
|
||||||
|
|
||||||
Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null");
|
Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null");
|
||||||
Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null");
|
Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null");
|
||||||
|
|
@ -136,7 +123,6 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
this.clientInboundChannel = clientInboundChannel;
|
this.clientInboundChannel = clientInboundChannel;
|
||||||
this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
|
this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
|
||||||
this.brokerTemplate = brokerTemplate;
|
this.brokerTemplate = brokerTemplate;
|
||||||
this.pathMatcher = new AntPathMatcher(pathSeparator);
|
|
||||||
|
|
||||||
Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
|
Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
|
||||||
converters.add(new StringMessageConverter());
|
converters.add(new StringMessageConverter());
|
||||||
|
|
@ -144,6 +130,36 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
this.messageConverter = new CompositeMessageConverter(converters);
|
this.messageConverter = new CompositeMessageConverter(converters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>Destination prefixes are expected to be slash-separated Strings and
|
||||||
|
* therefore a slash is automatically appended where missing to ensure a
|
||||||
|
* proper prefix-based match (i.e. matching complete segments).
|
||||||
|
*
|
||||||
|
* <p>Note however that the remaining portion of a destination after the
|
||||||
|
* prefix may use a different separator (e.g. commonly "." in messaging)
|
||||||
|
* depending on the configured {@code PathMatcher}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setDestinationPrefixes(Collection<String> prefixes) {
|
||||||
|
super.setDestinationPrefixes(appendSlashes(prefixes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<String> appendSlashes(Collection<String> prefixes) {
|
||||||
|
if (CollectionUtils.isEmpty(prefixes)) {
|
||||||
|
return prefixes;
|
||||||
|
}
|
||||||
|
Collection<String> result = new ArrayList<String>(prefixes.size());
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (!prefix.endsWith("/")) {
|
||||||
|
prefix = prefix + "/";
|
||||||
|
}
|
||||||
|
result.add(prefix);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a {@link MessageConverter} to use to convert the payload of a message
|
* Configure a {@link MessageConverter} to use to convert the payload of a message
|
||||||
* from serialize form with a specific MIME type to an Object matching the target
|
* from serialize form with a specific MIME type to an Object matching the target
|
||||||
|
|
@ -189,6 +205,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
||||||
this.pathMatcher = pathMatcher;
|
this.pathMatcher = pathMatcher;
|
||||||
|
this.slashPathSeparator = this.pathMatcher.combine("a", "a").equals("a/a");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -333,31 +350,31 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
|
MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
|
||||||
MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class);
|
MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class);
|
||||||
if (messageAnnot != null) {
|
if (messageAnnot != null) {
|
||||||
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot, typeAnnotation == null);
|
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot);
|
||||||
if (typeAnnotation != null) {
|
if (typeAnnotation != null) {
|
||||||
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
|
result = createMessageMappingCondition(typeAnnotation).combine(result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
SubscribeMapping subsribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
|
SubscribeMapping subsribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
|
||||||
if (subsribeAnnotation != null) {
|
if (subsribeAnnotation != null) {
|
||||||
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation, typeAnnotation == null);
|
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation);
|
||||||
if (typeAnnotation != null) {
|
if (typeAnnotation != null) {
|
||||||
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
|
result = createMessageMappingCondition(typeAnnotation).combine(result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation, boolean prependLeadingSlash) {
|
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation) {
|
||||||
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
|
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
|
||||||
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
|
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation, boolean prependLeadingSlash) {
|
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) {
|
||||||
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
|
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
|
||||||
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
|
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -376,6 +393,27 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
|
||||||
return SimpMessageHeaderAccessor.getDestination(message.getHeaders());
|
return SimpMessageHeaderAccessor.getDestination(message.getHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getLookupDestination(String destination) {
|
||||||
|
if (destination == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isEmpty(getDestinationPrefixes())) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
for (String prefix : getDestinationPrefixes()) {
|
||||||
|
if (destination.startsWith(prefix)) {
|
||||||
|
if (this.slashPathSeparator) {
|
||||||
|
return destination.substring(prefix.length() - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return destination.substring(prefix.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) {
|
protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) {
|
||||||
return mapping.getMatchingCondition(message);
|
return mapping.getMatchingCondition(message);
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
|
||||||
|
|
||||||
private SubscriptionRegistry subscriptionRegistry;
|
private SubscriptionRegistry subscriptionRegistry;
|
||||||
|
|
||||||
|
private PathMatcher pathMatcher;
|
||||||
|
|
||||||
private MessageHeaderInitializer headerInitializer;
|
private MessageHeaderInitializer headerInitializer;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,38 +60,21 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
|
||||||
* Create a SimpleBrokerMessageHandler instance with the given message channels
|
* Create a SimpleBrokerMessageHandler instance with the given message channels
|
||||||
* and destination prefixes.
|
* and destination prefixes.
|
||||||
*
|
*
|
||||||
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
|
* @param inChannel the channel for receiving messages from clients (e.g. WebSocket clients)
|
||||||
* @param clientOutboundChannel the channel for sending messages to clients (e.g. WebSocket clients)
|
* @param outChannel the channel for sending messages to clients (e.g. WebSocket clients)
|
||||||
* @param brokerChannel the channel for the application to send messages to the broker
|
* @param brokerChannel the channel for the application to send messages to the broker
|
||||||
*/
|
*/
|
||||||
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
|
public SimpleBrokerMessageHandler(SubscribableChannel inChannel, MessageChannel outChannel,
|
||||||
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes) {
|
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes) {
|
||||||
this(clientInboundChannel, clientOutboundChannel, brokerChannel, destinationPrefixes, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional constructor with a customized path matcher.
|
|
||||||
*
|
|
||||||
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
|
|
||||||
* @param clientOutboundChannel the channel for sending messages to clients (e.g. WebSocket clients)
|
|
||||||
* @param brokerChannel the channel for the application to send messages to the broker
|
|
||||||
* @param pathMatcher the path matcher to use
|
|
||||||
* @since 4.1
|
|
||||||
*/
|
|
||||||
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
|
|
||||||
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes, PathMatcher pathMatcher) {
|
|
||||||
|
|
||||||
super(destinationPrefixes);
|
super(destinationPrefixes);
|
||||||
Assert.notNull(clientInboundChannel, "'clientInboundChannel' must not be null");
|
Assert.notNull(inChannel, "'clientInboundChannel' must not be null");
|
||||||
Assert.notNull(clientOutboundChannel, "'clientOutboundChannel' must not be null");
|
Assert.notNull(outChannel, "'clientOutboundChannel' must not be null");
|
||||||
Assert.notNull(brokerChannel, "'brokerChannel' must not be null");
|
Assert.notNull(brokerChannel, "'brokerChannel' must not be null");
|
||||||
this.clientInboundChannel = clientInboundChannel;
|
this.clientInboundChannel = inChannel;
|
||||||
this.clientOutboundChannel = clientOutboundChannel;
|
this.clientOutboundChannel = outChannel;
|
||||||
this.brokerChannel = brokerChannel;
|
this.brokerChannel = brokerChannel;
|
||||||
DefaultSubscriptionRegistry subscriptionRegistry = new DefaultSubscriptionRegistry();
|
DefaultSubscriptionRegistry subscriptionRegistry = new DefaultSubscriptionRegistry();
|
||||||
if(pathMatcher != null) {
|
|
||||||
subscriptionRegistry.setPathMatcher(pathMatcher);
|
|
||||||
}
|
|
||||||
this.subscriptionRegistry = subscriptionRegistry;
|
this.subscriptionRegistry = subscriptionRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,15 +91,41 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
|
||||||
return this.brokerChannel;
|
return this.brokerChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a custom SubscriptionRegistry to use for storing subscriptions.
|
||||||
|
*
|
||||||
|
* <p><strong>Note</strong> that when a custom PathMatcher is configured via
|
||||||
|
* {@link #setPathMatcher}, if the custom registry is not an instance of
|
||||||
|
* {@link DefaultSubscriptionRegistry}, the provided PathMatcher is not used
|
||||||
|
* and must be configured directly on the custom registry.
|
||||||
|
*/
|
||||||
public void setSubscriptionRegistry(SubscriptionRegistry subscriptionRegistry) {
|
public void setSubscriptionRegistry(SubscriptionRegistry subscriptionRegistry) {
|
||||||
Assert.notNull(subscriptionRegistry, "SubscriptionRegistry must not be null");
|
Assert.notNull(subscriptionRegistry, "SubscriptionRegistry must not be null");
|
||||||
this.subscriptionRegistry = subscriptionRegistry;
|
this.subscriptionRegistry = subscriptionRegistry;
|
||||||
|
initPathMatcherToUse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPathMatcherToUse() {
|
||||||
|
if (this.pathMatcher != null) {
|
||||||
|
if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {
|
||||||
|
((DefaultSubscriptionRegistry) this.subscriptionRegistry).setPathMatcher(this.pathMatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubscriptionRegistry getSubscriptionRegistry() {
|
public SubscriptionRegistry getSubscriptionRegistry() {
|
||||||
return this.subscriptionRegistry;
|
return this.subscriptionRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When configured, the given PathMatcher is passed down to the
|
||||||
|
* SubscriptionRegistry to use for matching destination to subscriptions.
|
||||||
|
*/
|
||||||
|
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||||
|
this.pathMatcher = pathMatcher;
|
||||||
|
initPathMatcherToUse();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a {@link MessageHeaderInitializer} to apply to the headers of all
|
* Configure a {@link MessageHeaderInitializer} to apply to the headers of all
|
||||||
* messages sent to the client outbound channel.
|
* messages sent to the client outbound channel.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2014 the original author or authors.
|
* Copyright 2002-2013 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -23,6 +23,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.springframework.messaging.MessageChannel;
|
import org.springframework.messaging.MessageChannel;
|
||||||
import org.springframework.messaging.SubscribableChannel;
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
|
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -66,4 +67,6 @@ public abstract class AbstractBrokerRegistration {
|
||||||
return this.destinationPrefixes;
|
return this.destinationPrefixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract AbstractBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ import org.springframework.messaging.simp.user.UserSessionRegistry;
|
||||||
import org.springframework.messaging.support.AbstractSubscribableChannel;
|
import org.springframework.messaging.support.AbstractSubscribableChannel;
|
||||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
|
import org.springframework.util.PathMatcher;
|
||||||
import org.springframework.validation.Errors;
|
import org.springframework.validation.Errors;
|
||||||
import org.springframework.validation.Validator;
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
|
@ -206,17 +206,17 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
|
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
|
||||||
|
|
||||||
String defaultSeparator = this.getBrokerRegistry().getDefaultSeparator();
|
|
||||||
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
|
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
|
||||||
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate(), defaultSeparator);
|
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
|
||||||
|
|
||||||
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
|
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
|
||||||
handler.setMessageConverter(brokerMessageConverter());
|
handler.setMessageConverter(brokerMessageConverter());
|
||||||
handler.setValidator(simpValidator());
|
handler.setValidator(simpValidator());
|
||||||
|
|
||||||
AntPathMatcher pathMatcher = new AntPathMatcher(defaultSeparator);
|
PathMatcher pathMatcher = this.getBrokerRegistry().getPathMatcher();
|
||||||
handler.setPathMatcher(pathMatcher);
|
if (pathMatcher != null) {
|
||||||
|
handler.setPathMatcher(pathMatcher);
|
||||||
|
}
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,9 +234,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UserDestinationMessageHandler userDestinationMessageHandler() {
|
public UserDestinationMessageHandler userDestinationMessageHandler() {
|
||||||
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(
|
return new UserDestinationMessageHandler(clientInboundChannel(), brokerChannel(), userDestinationResolver());
|
||||||
clientInboundChannel(), brokerChannel(), userDestinationResolver());
|
|
||||||
return handler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
||||||
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.PathMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A registry for configuring message broker options.
|
* A registry for configuring message broker options.
|
||||||
|
|
@ -47,9 +48,10 @@ public class MessageBrokerRegistry {
|
||||||
|
|
||||||
private String userDestinationPrefix;
|
private String userDestinationPrefix;
|
||||||
|
|
||||||
|
private PathMatcher pathMatcher;
|
||||||
|
|
||||||
private ChannelRegistration brokerChannelRegistration = new ChannelRegistration();
|
private ChannelRegistration brokerChannelRegistration = new ChannelRegistration();
|
||||||
|
|
||||||
private String defaultSeparator;
|
|
||||||
|
|
||||||
public MessageBrokerRegistry(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
|
public MessageBrokerRegistry(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
|
||||||
Assert.notNull(clientInboundChannel);
|
Assert.notNull(clientInboundChannel);
|
||||||
|
|
@ -111,6 +113,31 @@ public class MessageBrokerRegistry {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the PathMatcher to use to match the destinations of incoming
|
||||||
|
* messages to {@code @MessageMapping} and {@code @SubscribeMapping} methods.
|
||||||
|
*
|
||||||
|
* <p>By default {@link org.springframework.util.AntPathMatcher} is configured.
|
||||||
|
* However applications may provide an {@code AntPathMatcher} instance
|
||||||
|
* customized to use "." (commonly used in messaging) instead of "/" as path
|
||||||
|
* separator or provide a completely different PathMatcher implementation.
|
||||||
|
*
|
||||||
|
* <p>Note that the configured PathMatcher is only used for matching the
|
||||||
|
* portion of the destination after the configured prefix. For example given
|
||||||
|
* application destination prefix "/app" and destination "/app/price.stock.**",
|
||||||
|
* the message might be mapped to a controller with "price" and "stock.**"
|
||||||
|
* as its type and method-level mappings respectively.
|
||||||
|
*
|
||||||
|
* <p>When the simple broker is enabled, the PathMatcher configured here is
|
||||||
|
* also used to match message destinations when brokering messages.
|
||||||
|
*
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
public MessageBrokerRegistry setPathMatcher(PathMatcher pathMatcher) {
|
||||||
|
this.pathMatcher = pathMatcher;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customize the channel used to send messages from the application to the message
|
* Customize the channel used to send messages from the application to the message
|
||||||
* broker. By default messages from the application to the message broker are sent
|
* broker. By default messages from the application to the message broker are sent
|
||||||
|
|
@ -122,24 +149,14 @@ public class MessageBrokerRegistry {
|
||||||
return this.brokerChannelRegistration;
|
return this.brokerChannelRegistration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Customize the default separator used for destination patterns matching/combining.
|
|
||||||
* It can be used to configure "." as the default separator, since it is used in most
|
|
||||||
* STOMP broker relay, enabling destination patterns like "/topic/PRICE.STOCK.**".
|
|
||||||
* <p>The default separator is "/".
|
|
||||||
*/
|
|
||||||
public MessageBrokerRegistry defaultSeparator(String defaultSeparator) {
|
|
||||||
this.defaultSeparator = defaultSeparator;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SimpleBrokerMessageHandler getSimpleBroker(SubscribableChannel brokerChannel) {
|
protected SimpleBrokerMessageHandler getSimpleBroker(SubscribableChannel brokerChannel) {
|
||||||
if ((this.simpleBrokerRegistration == null) && (this.brokerRelayRegistration == null)) {
|
if ((this.simpleBrokerRegistration == null) && (this.brokerRelayRegistration == null)) {
|
||||||
enableSimpleBroker();
|
enableSimpleBroker();
|
||||||
}
|
}
|
||||||
if (this.simpleBrokerRegistration != null) {
|
if (this.simpleBrokerRegistration != null) {
|
||||||
AntPathMatcher pathMatcher = new AntPathMatcher(this.defaultSeparator);
|
SimpleBrokerMessageHandler handler = this.simpleBrokerRegistration.getMessageHandler(brokerChannel);
|
||||||
return this.simpleBrokerRegistration.getMessageHandler(brokerChannel, pathMatcher);
|
handler.setPathMatcher(this.pathMatcher);
|
||||||
|
return handler;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -160,12 +177,12 @@ public class MessageBrokerRegistry {
|
||||||
return this.userDestinationPrefix;
|
return this.userDestinationPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PathMatcher getPathMatcher() {
|
||||||
|
return this.pathMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
protected ChannelRegistration getBrokerChannelRegistration() {
|
protected ChannelRegistration getBrokerChannelRegistration() {
|
||||||
return this.brokerChannelRegistration;
|
return this.brokerChannelRegistration;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getDefaultSeparator() {
|
|
||||||
return this.defaultSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,28 +19,25 @@ package org.springframework.messaging.simp.config;
|
||||||
import org.springframework.messaging.MessageChannel;
|
import org.springframework.messaging.MessageChannel;
|
||||||
import org.springframework.messaging.SubscribableChannel;
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registration class for configuring a {@link SimpleBrokerMessageHandler}.
|
* Registration class for configuring a {@link SimpleBrokerMessageHandler}.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Sebastien Deleuze
|
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class SimpleBrokerRegistration extends AbstractBrokerRegistration {
|
public class SimpleBrokerRegistration extends AbstractBrokerRegistration {
|
||||||
|
|
||||||
|
|
||||||
public SimpleBrokerRegistration(SubscribableChannel clientInboundChannel,
|
public SimpleBrokerRegistration(SubscribableChannel inChannel, MessageChannel outChannel, String[] prefixes) {
|
||||||
MessageChannel clientOutboundChannel, String[] destinationPrefixes) {
|
super(inChannel, outChannel, prefixes);
|
||||||
|
|
||||||
super(clientInboundChannel, clientOutboundChannel, destinationPrefixes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel, PathMatcher pathMatcher) {
|
@Override
|
||||||
|
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
|
||||||
return new SimpleBrokerMessageHandler(getClientInboundChannel(),
|
return new SimpleBrokerMessageHandler(getClientInboundChannel(),
|
||||||
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes(), pathMatcher);
|
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2014 the original author or authors.
|
* Copyright 2002-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -18,42 +18,34 @@ package org.springframework.messaging.handler;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.Parameterized;
|
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
|
||||||
import org.junit.runners.Parameterized.Parameter;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link DestinationPatternsMessageCondition}.
|
* Unit tests for {@link DestinationPatternsMessageCondition}.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Sebastien Deleuze
|
|
||||||
*/
|
*/
|
||||||
@RunWith(Parameterized.class)
|
|
||||||
public class DestinationPatternsMessageConditionTests {
|
public class DestinationPatternsMessageConditionTests {
|
||||||
|
|
||||||
@Parameter(0)
|
|
||||||
public String pathSeparator;
|
|
||||||
|
|
||||||
@Parameters
|
|
||||||
public static Iterable<Object[]> arguments() {
|
|
||||||
return Arrays.asList(new Object[][]{{"/"}, {"."}});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void prependSlash() {
|
public void prependSlash() {
|
||||||
DestinationPatternsMessageCondition c = condition("foo");
|
DestinationPatternsMessageCondition c = condition("foo");
|
||||||
assertEquals("/foo", c.getPatterns().iterator().next());
|
assertEquals("/foo", c.getPatterns().iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void prependSlashWithCustomPathSeparator() {
|
||||||
|
DestinationPatternsMessageCondition c =
|
||||||
|
new DestinationPatternsMessageCondition(new String[] {"foo"}, new AntPathMatcher("."));
|
||||||
|
|
||||||
|
assertEquals("Pre-pending should be disabled when not using '/' as path separator",
|
||||||
|
"foo", c.getPatterns().iterator().next());
|
||||||
|
}
|
||||||
|
|
||||||
// SPR-8255
|
// SPR-8255
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -65,21 +57,20 @@ public class DestinationPatternsMessageConditionTests {
|
||||||
@Test
|
@Test
|
||||||
public void combineEmptySets() {
|
public void combineEmptySets() {
|
||||||
DestinationPatternsMessageCondition c1 = condition();
|
DestinationPatternsMessageCondition c1 = condition();
|
||||||
DestinationPatternsMessageCondition c2 = suffixCondition();
|
DestinationPatternsMessageCondition c2 = condition();
|
||||||
|
|
||||||
assertEquals(condition(""), c1.combine(c2));
|
assertEquals(condition(""), c1.combine(c2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void combineOnePatternWithEmptySet() {
|
public void combineOnePatternWithEmptySet() {
|
||||||
DestinationPatternsMessageCondition c1 = condition("/type1",
|
DestinationPatternsMessageCondition c1 = condition("/type1", "/type2");
|
||||||
pathSeparator + "type2");
|
DestinationPatternsMessageCondition c2 = condition();
|
||||||
DestinationPatternsMessageCondition c2 = suffixCondition();
|
|
||||||
|
|
||||||
assertEquals(condition("/type1", pathSeparator + "type2"), c1.combine(c2));
|
assertEquals(condition("/type1", "/type2"), c1.combine(c2));
|
||||||
|
|
||||||
c1 = condition();
|
c1 = condition();
|
||||||
c2 = suffixCondition("/method1", "/method2");
|
c2 = condition("/method1", "/method2");
|
||||||
|
|
||||||
assertEquals(condition("/method1", "/method2"), c1.combine(c2));
|
assertEquals(condition("/method1", "/method2"), c1.combine(c2));
|
||||||
}
|
}
|
||||||
|
|
@ -87,12 +78,10 @@ public class DestinationPatternsMessageConditionTests {
|
||||||
@Test
|
@Test
|
||||||
public void combineMultiplePatterns() {
|
public void combineMultiplePatterns() {
|
||||||
DestinationPatternsMessageCondition c1 = condition("/t1", "/t2");
|
DestinationPatternsMessageCondition c1 = condition("/t1", "/t2");
|
||||||
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "m1",
|
DestinationPatternsMessageCondition c2 = condition("/m1", "/m2");
|
||||||
pathSeparator + "m2");
|
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(new DestinationPatternsMessageCondition(
|
||||||
condition("/t1" + pathSeparator + "m1", "/t1" + pathSeparator + "m2",
|
"/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"), c1.combine(c2));
|
||||||
"/t2" + pathSeparator + "m1", "/t2" + pathSeparator + "m2"), c1.combine(c2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -105,40 +94,35 @@ public class DestinationPatternsMessageConditionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchPattern() {
|
public void matchPattern() {
|
||||||
DestinationPatternsMessageCondition condition = condition(
|
DestinationPatternsMessageCondition condition = condition("/foo/*");
|
||||||
"/foo" + pathSeparator + "*");
|
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo/bar"));
|
||||||
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo" + pathSeparator + "bar"));
|
|
||||||
|
|
||||||
assertNotNull(match);
|
assertNotNull(match);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchSortPatterns() {
|
public void matchSortPatterns() {
|
||||||
DestinationPatternsMessageCondition condition = suffixCondition(
|
DestinationPatternsMessageCondition condition = condition("/**", "/foo/bar", "/foo/*");
|
||||||
pathSeparator + "**", pathSeparator + "foo" + pathSeparator + "bar",
|
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo/bar"));
|
||||||
pathSeparator + "foo" + pathSeparator + "*");
|
DestinationPatternsMessageCondition expected = condition("/foo/bar", "/foo/*", "/**");
|
||||||
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo(pathSeparator + "foo" + pathSeparator + "bar"));
|
|
||||||
DestinationPatternsMessageCondition expected = suffixCondition(
|
|
||||||
pathSeparator + "foo" + pathSeparator + "bar",
|
|
||||||
pathSeparator + "foo" + pathSeparator + "*", pathSeparator + "**");
|
|
||||||
|
|
||||||
assertEquals(expected, match);
|
assertEquals(expected, match);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compareEqualPatterns() {
|
public void compareEqualPatterns() {
|
||||||
DestinationPatternsMessageCondition c1 = suffixCondition(pathSeparator + "foo*");
|
DestinationPatternsMessageCondition c1 = condition("/foo*");
|
||||||
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "foo*");
|
DestinationPatternsMessageCondition c2 = condition("/foo*");
|
||||||
|
|
||||||
assertEquals(0, c1.compareTo(c2, messageTo(pathSeparator + "foo")));
|
assertEquals(0, c1.compareTo(c2, messageTo("/foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void comparePatternSpecificity() {
|
public void comparePatternSpecificity() {
|
||||||
DestinationPatternsMessageCondition c1 = suffixCondition(pathSeparator + "fo*");
|
DestinationPatternsMessageCondition c1 = condition("/fo*");
|
||||||
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "foo");
|
DestinationPatternsMessageCondition c2 = condition("/foo");
|
||||||
|
|
||||||
assertEquals(1, c1.compareTo(c2, messageTo(pathSeparator + "foo")));
|
assertEquals(1, c1.compareTo(c2, messageTo("/foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -156,11 +140,7 @@ public class DestinationPatternsMessageConditionTests {
|
||||||
|
|
||||||
|
|
||||||
private DestinationPatternsMessageCondition condition(String... patterns) {
|
private DestinationPatternsMessageCondition condition(String... patterns) {
|
||||||
return new DestinationPatternsMessageCondition(patterns, new AntPathMatcher(this.pathSeparator));
|
return new DestinationPatternsMessageCondition(patterns);
|
||||||
}
|
|
||||||
|
|
||||||
private DestinationPatternsMessageCondition suffixCondition(String... patterns) {
|
|
||||||
return new DestinationPatternsMessageCondition(patterns, new AntPathMatcher(this.pathSeparator), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Message<?> messageTo(String destination) {
|
private Message<?> messageTo(String destination) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.messaging.simp.annotation.support;
|
package org.springframework.messaging.simp.annotation.support;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
@ -38,6 +40,7 @@ import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
import org.springframework.messaging.simp.annotation.SubscribeMapping;
|
import org.springframework.messaging.simp.annotation.SubscribeMapping;
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.validation.Errors;
|
import org.springframework.validation.Errors;
|
||||||
import org.springframework.validation.Validator;
|
import org.springframework.validation.Validator;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
@ -148,7 +151,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void simpScope() {
|
public void simpScope() {
|
||||||
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
|
Map<String, Object> map = new ConcurrentHashMap<>();
|
||||||
map.put("name", "value");
|
map.put("name", "value");
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
||||||
headers.setSessionId("session1");
|
headers.setSessionId("session1");
|
||||||
|
|
@ -160,6 +163,33 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||||
assertEquals("scope", this.testController.method);
|
assertEquals("scope", this.testController.method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dotPathSeparator() {
|
||||||
|
DotPathSeparatorController controller = new DotPathSeparatorController();
|
||||||
|
|
||||||
|
this.messageHandler.setPathMatcher(new AntPathMatcher("."));
|
||||||
|
this.messageHandler.registerHandler(controller);
|
||||||
|
this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/"));
|
||||||
|
|
||||||
|
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
||||||
|
headers.setSessionId("session1");
|
||||||
|
headers.setSessionAttributes(new HashMap<>());
|
||||||
|
headers.setDestination("/app1/pre.foo");
|
||||||
|
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
||||||
|
this.messageHandler.handleMessage(message);
|
||||||
|
|
||||||
|
assertEquals("handleFoo", controller.method);
|
||||||
|
|
||||||
|
headers = SimpMessageHeaderAccessor.create();
|
||||||
|
headers.setSessionId("session1");
|
||||||
|
headers.setSessionAttributes(new HashMap<>());
|
||||||
|
headers.setDestination("/app2/pre.foo");
|
||||||
|
message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
||||||
|
this.messageHandler.handleMessage(message);
|
||||||
|
|
||||||
|
assertEquals("handleFoo", controller.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
|
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
|
||||||
|
|
||||||
|
|
@ -232,6 +262,20 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@MessageMapping("pre")
|
||||||
|
private static class DotPathSeparatorController {
|
||||||
|
|
||||||
|
private String method;
|
||||||
|
|
||||||
|
|
||||||
|
@MessageMapping("foo")
|
||||||
|
public void handleFoo() {
|
||||||
|
this.method = "handleFoo";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class StringTestValidator implements Validator {
|
private static class StringTestValidator implements Validator {
|
||||||
|
|
||||||
private final String invalidValue;
|
private final String invalidValue;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ public class MessageBrokerConfigurationTests {
|
||||||
|
|
||||||
private AnnotationConfigApplicationContext customChannelContext;
|
private AnnotationConfigApplicationContext customChannelContext;
|
||||||
|
|
||||||
private AnnotationConfigApplicationContext customMatchingContext;
|
private AnnotationConfigApplicationContext customPathMatcherContext;
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
@ -101,9 +101,9 @@ public class MessageBrokerConfigurationTests {
|
||||||
this.customChannelContext.register(CustomChannelConfig.class);
|
this.customChannelContext.register(CustomChannelConfig.class);
|
||||||
this.customChannelContext.refresh();
|
this.customChannelContext.refresh();
|
||||||
|
|
||||||
this.customMatchingContext = new AnnotationConfigApplicationContext();
|
this.customPathMatcherContext = new AnnotationConfigApplicationContext();
|
||||||
this.customMatchingContext.register(CustomMatchingSimpleBrokerConfig.class);
|
this.customPathMatcherContext.register(CustomPathMatcherConfig.class);
|
||||||
this.customMatchingContext.refresh();
|
this.customPathMatcherContext.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -407,17 +407,13 @@ public class MessageBrokerConfigurationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void customMatching() {
|
public void customPathMatcher() {
|
||||||
SimpleBrokerMessageHandler brokerHandler = this.customMatchingContext.getBean(SimpleBrokerMessageHandler.class);
|
SimpleBrokerMessageHandler broker = this.customPathMatcherContext.getBean(SimpleBrokerMessageHandler.class);
|
||||||
DefaultSubscriptionRegistry subscriptionRegistry = (DefaultSubscriptionRegistry)brokerHandler.getSubscriptionRegistry();
|
DefaultSubscriptionRegistry registry = (DefaultSubscriptionRegistry) broker.getSubscriptionRegistry();
|
||||||
AntPathMatcher pathMatcher = (AntPathMatcher)subscriptionRegistry.getPathMatcher();
|
assertEquals("a.a", registry.getPathMatcher().combine("a", "a"));
|
||||||
DirectFieldAccessor accessor = new DirectFieldAccessor(pathMatcher);
|
|
||||||
assertEquals(".", accessor.getPropertyValue("pathSeparator"));
|
|
||||||
|
|
||||||
SimpAnnotationMethodMessageHandler messageHandler = customMatchingContext.getBean(SimpAnnotationMethodMessageHandler.class);
|
SimpAnnotationMethodMessageHandler handler = this.customPathMatcherContext.getBean(SimpAnnotationMethodMessageHandler.class);
|
||||||
pathMatcher = (AntPathMatcher)messageHandler.getPathMatcher();
|
assertEquals("a.a", handler.getPathMatcher().combine("a", "a"));
|
||||||
accessor = new DirectFieldAccessor(pathMatcher);
|
|
||||||
assertEquals(".", accessor.getPropertyValue("pathSeparator"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -504,11 +500,11 @@ public class MessageBrokerConfigurationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class CustomMatchingSimpleBrokerConfig extends SimpleBrokerConfig {
|
static class CustomPathMatcherConfig extends SimpleBrokerConfig {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||||
registry.defaultSeparator(".").enableSimpleBroker("/topic", "/queue");
|
registry.setPathMatcher(new AntPathMatcher(".")).enableSimpleBroker("/topic", "/queue");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -330,11 +330,11 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
if (simpleBrokerElem != null) {
|
if (simpleBrokerElem != null) {
|
||||||
String prefix = simpleBrokerElem.getAttribute("prefix");
|
String prefix = simpleBrokerElem.getAttribute("prefix");
|
||||||
cavs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
|
cavs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
|
||||||
String defaultSeparator = messageBrokerElement.getAttribute("default-separator");
|
|
||||||
if (!defaultSeparator.isEmpty()) {
|
|
||||||
cavs.addIndexedArgumentValue(4, new AntPathMatcher(defaultSeparator));
|
|
||||||
}
|
|
||||||
brokerDef = new RootBeanDefinition(SimpleBrokerMessageHandler.class, cavs, null);
|
brokerDef = new RootBeanDefinition(SimpleBrokerMessageHandler.class, cavs, null);
|
||||||
|
if (messageBrokerElement.hasAttribute("path-matcher")) {
|
||||||
|
brokerDef.getPropertyValues().add("pathMatcher",
|
||||||
|
new RuntimeBeanReference(messageBrokerElement.getAttribute("path-matcher")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (brokerRelayElem != null) {
|
else if (brokerRelayElem != null) {
|
||||||
String prefix = brokerRelayElem.getAttribute("prefix");
|
String prefix = brokerRelayElem.getAttribute("prefix");
|
||||||
|
|
@ -454,15 +454,13 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
mpvs.add("destinationPrefixes",Arrays.asList(StringUtils.tokenizeToStringArray(appDestPrefix, ",")));
|
mpvs.add("destinationPrefixes",Arrays.asList(StringUtils.tokenizeToStringArray(appDestPrefix, ",")));
|
||||||
mpvs.add("messageConverter", brokerMessageConverterRef);
|
mpvs.add("messageConverter", brokerMessageConverterRef);
|
||||||
|
|
||||||
RootBeanDefinition annotationMethodMessageHandlerDef =
|
RootBeanDefinition beanDef = new RootBeanDefinition(SimpAnnotationMethodMessageHandler.class, cavs, mpvs);
|
||||||
new RootBeanDefinition(SimpAnnotationMethodMessageHandler.class, cavs, mpvs);
|
if (messageBrokerElement.hasAttribute("path-matcher")) {
|
||||||
|
beanDef.getPropertyValues().add("pathMatcher",
|
||||||
String defaultSeparator = messageBrokerElement.getAttribute("default-separator");
|
new RuntimeBeanReference(messageBrokerElement.getAttribute("path-matcher")));
|
||||||
if (!defaultSeparator.isEmpty()) {
|
|
||||||
annotationMethodMessageHandlerDef.getPropertyValues().add("pathMatcher", new AntPathMatcher(defaultSeparator));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerBeanDef(annotationMethodMessageHandlerDef, parserCxt, source);
|
registerBeanDef(beanDef, parserCxt, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RuntimeBeanReference registerUserDestinationResolver(Element messageBrokerElement,
|
private RuntimeBeanReference registerUserDestinationResolver(Element messageBrokerElement,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
~ Copyright 2002-2013 the original author or authors.
|
~ Copyright 2002-2014 the original author or authors.
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
~ you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
|
|
@ -19,12 +19,13 @@
|
||||||
<xsd:schema xmlns="http://www.springframework.org/schema/websocket"
|
<xsd:schema xmlns="http://www.springframework.org/schema/websocket"
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:tool="http://www.springframework.org/schema/tool"
|
||||||
targetNamespace="http://www.springframework.org/schema/websocket"
|
targetNamespace="http://www.springframework.org/schema/websocket"
|
||||||
elementFormDefault="qualified"
|
elementFormDefault="qualified"
|
||||||
attributeFormDefault="unqualified">
|
attributeFormDefault="unqualified">
|
||||||
|
|
||||||
<xsd:import namespace="http://www.springframework.org/schema/beans"
|
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"/>
|
||||||
schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"/>
|
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.1.xsd" />
|
||||||
|
|
||||||
<xsd:complexType name="mapping">
|
<xsd:complexType name="mapping">
|
||||||
<xsd:annotation>
|
<xsd:annotation>
|
||||||
|
|
@ -687,14 +688,31 @@
|
||||||
]]></xsd:documentation>
|
]]></xsd:documentation>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
</xsd:attribute>
|
</xsd:attribute>
|
||||||
<xsd:attribute name="default-separator" type="xsd:string">
|
<xsd:attribute name="path-matcher" type="xsd:string">
|
||||||
<xsd:annotation>
|
<xsd:annotation>
|
||||||
<xsd:documentation><![CDATA[
|
<xsd:documentation><![CDATA[
|
||||||
Customize the default separator used for destination patterns matching/combining.
|
A reference to the PathMatcher to use to match the destinations of incoming
|
||||||
It could be used to configure "." as the default separator, since it is used in most
|
messages to @MessageMapping and @SubscribeMapping methods.
|
||||||
STOMP broker relay, enabling destination patterns like "/topic/PRICE.STOCK.**".
|
|
||||||
The default separator is "/".
|
By default AntPathMatcher is configured.
|
||||||
|
However applications may provide an AntPathMatcher instance
|
||||||
|
customized to use "." (commonly used in messaging) instead of "/" as path
|
||||||
|
separator or provide a completely different PathMatcher implementation.
|
||||||
|
|
||||||
|
Note that the configured PathMatcher is only used for matching the
|
||||||
|
portion of the destination after the configured prefix. For example given
|
||||||
|
application destination prefix "/app" and destination "/app/price.stock.**",
|
||||||
|
the message might be mapped to a controller with "price" and "stock.**"
|
||||||
|
as its type and method-level mappings respectively.
|
||||||
|
|
||||||
|
When the simple broker is enabled, the PathMatcher configured here is
|
||||||
|
also used to match message destinations when brokering messages.
|
||||||
]]></xsd:documentation>
|
]]></xsd:documentation>
|
||||||
|
<xsd:appinfo>
|
||||||
|
<tool:annotation kind="ref">
|
||||||
|
<tool:expected-type type="java:org.springframework.util.PathMatcher" />
|
||||||
|
</tool:annotation>
|
||||||
|
</xsd:appinfo>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
</xsd:attribute>
|
</xsd:attribute>
|
||||||
<xsd:attribute name="order" type="xsd:token">
|
<xsd:attribute name="order" type="xsd:token">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:message-broker>
|
<websocket:message-broker>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:message-broker>
|
<websocket:message-broker>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:message-broker>
|
<websocket:message-broker>
|
||||||
<websocket:stomp-endpoint path="/foo"/>
|
<websocket:stomp-endpoint path="/foo"/>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal">
|
<websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal">
|
||||||
<websocket:stomp-endpoint path="/foo,/bar">
|
<websocket:stomp-endpoint path="/foo,/bar">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:message-broker order="2">
|
<websocket:message-broker order="2">
|
||||||
<websocket:stomp-endpoint path="/foo">
|
<websocket:stomp-endpoint path="/foo">
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal" default-separator=".">
|
<websocket:message-broker application-destination-prefix="/app"
|
||||||
|
user-destination-prefix="/personal"
|
||||||
|
path-matcher="pathMatcher">
|
||||||
|
|
||||||
<!-- message-size=128*1024, send-buffer-size=1024*1024 -->
|
<!-- message-size=128*1024, send-buffer-size=1024*1024 -->
|
||||||
<websocket:transport message-size="131072" send-timeout="25000" send-buffer-size="1048576" />
|
<websocket:transport message-size="131072" send-timeout="25000" send-buffer-size="1048576" />
|
||||||
|
|
@ -22,6 +24,10 @@
|
||||||
|
|
||||||
</websocket:message-broker>
|
</websocket:message-broker>
|
||||||
|
|
||||||
|
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
|
||||||
|
<property name="pathSeparator" value="." />
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="myHandler" class="org.springframework.web.socket.config.TestHandshakeHandler"/>
|
<bean id="myHandler" class="org.springframework.web.socket.config.TestHandshakeHandler"/>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:handlers order="2">
|
<websocket:handlers order="2">
|
||||||
<websocket:mapping path="/foo" handler="fooHandler"/>
|
<websocket:mapping path="/foo" handler="fooHandler"/>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:handlers >
|
<websocket:handlers >
|
||||||
<websocket:mapping path="/test" handler="testHandler"/>
|
<websocket:mapping path="/test" handler="testHandler"/>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:handlers>
|
<websocket:handlers>
|
||||||
<websocket:mapping path="/test" handler="testHandler"/>
|
<websocket:mapping path="/test" handler="testHandler"/>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
|
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||||
|
|
||||||
<websocket:handlers>
|
<websocket:handlers>
|
||||||
<websocket:mapping path="/foo,/bar" handler="fooHandler"/>
|
<websocket:mapping path="/foo,/bar" handler="fooHandler"/>
|
||||||
|
|
|
||||||
|
|
@ -37864,8 +37864,8 @@ options. The endpoint is available for clients to connect to at URL path `/app/p
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||||
config.setApplicationDestinationPrefixes("/app")
|
config.setApplicationDestinationPrefixes("/app");
|
||||||
.enableSimpleBroker("/queue", "/topic");
|
config.enableSimpleBroker("/queue", "/topic");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -38001,7 +38001,7 @@ Below is a simple example to illustrate the flow of messages:
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||||
registry.setApplicationDestinationPrefixes("/app");
|
registry.setApplicationDestinationPrefixes("/app");
|
||||||
registry.enableSimpleBroker("/topic/");
|
registry.enableSimpleBroker("/topic");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -38039,17 +38039,28 @@ kinds of arguments and return values supported.
|
||||||
[[websocket-stomp-handle-annotations]]
|
[[websocket-stomp-handle-annotations]]
|
||||||
==== Annotation Message Handling
|
==== Annotation Message Handling
|
||||||
|
|
||||||
The `@MessageMapping` annotation is supported on methods of `@Controller`
|
The `@MessageMapping` annotation is supported on methods of `@Controller` classes.
|
||||||
as well as on `@RestController`-annotated classes.
|
It can be used for mapping methods to message destinations and can also be combined
|
||||||
It can be used for mapping methods to path-like message destinations. It is also
|
with the type-level `@MessageMapping` for expressing shared mappings across all
|
||||||
possible to combine with a type-level `@MessageMapping` for expressing shared
|
annotated methods within a controller.
|
||||||
mappings across all annotated methods within a controller.
|
|
||||||
|
|
||||||
Destination mappings can contain Ant-style patterns (e.g. "/foo*", "/foo/**")
|
By default destination mappings are treated as Ant-style, slash-separated, path
|
||||||
and template variables (e.g. "/foo/{id}"), which can then be accessed via
|
patterns, e.g. "/foo*", "/foo/**". etc. They can also contain template variables,
|
||||||
`@DestinationVariable` method arguments. This should be familiar to Spring MVC
|
e.g. "/foo/{id}" that can then be referenced via `@DestinationVariable`-annotated
|
||||||
users, in fact the same `AntPathMatcher` is used for matching destinations based
|
method arguments.
|
||||||
on patterns and for extracting template variables.
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
Although Ant-style, slash-separated, path patterns should feel familiar to web
|
||||||
|
developers, in message brokers and in messaging it is common to use "." as the
|
||||||
|
separator, for example in the names of destinations such as topics, queues,
|
||||||
|
exchanges, etc.
|
||||||
|
|
||||||
|
Applications can switch to using "." (dot) instead of "/" (slash) as the separator
|
||||||
|
for destinations mapped to `@MessageMapping` methods simply by configuring an `AntPathMatcher`
|
||||||
|
with a customized path separator property. This can be done easily through
|
||||||
|
the provided Java config and XML namespace.
|
||||||
|
====
|
||||||
|
|
||||||
The following method arguments are supported for `@MessageMapping` methods:
|
The following method arguments are supported for `@MessageMapping` methods:
|
||||||
|
|
||||||
|
|
@ -38137,6 +38148,21 @@ stores them in memory, and broadcasts messages to connected clients with matchin
|
||||||
destinations. The broker supports path-like destinations, including subscriptions
|
destinations. The broker supports path-like destinations, including subscriptions
|
||||||
to Ant-style destination patterns.
|
to Ant-style destination patterns.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
Although Ant-style, slash-separated, path patterns should feel familiar to web
|
||||||
|
developers, in message brokers and in messaging it is common to use "." as the
|
||||||
|
separator, for example in the names of destinations such as topics, queues,
|
||||||
|
exchanges, etc.
|
||||||
|
|
||||||
|
Applications can switch to using "." (dot) instead of "/" (slash) as the separator
|
||||||
|
for destinations handled by the broker simply by configuring an `AntPathMatcher`
|
||||||
|
with a customized path separator property. This can be done easily through
|
||||||
|
the provided Java config and XML namespace.
|
||||||
|
====
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[websocket-stomp-handle-broker-relay]]
|
[[websocket-stomp-handle-broker-relay]]
|
||||||
==== Full-Featured Broker
|
==== Full-Featured Broker
|
||||||
|
|
@ -38168,7 +38194,7 @@ Below is example configuration that enables a full-featured broker:
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||||
registry.enableStompBrokerRelay("/topic/", "/queue/");
|
registry.enableStompBrokerRelay("/topic", "/queue");
|
||||||
registry.setApplicationDestinationPrefixes("/app");
|
registry.setApplicationDestinationPrefixes("/app");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue