Allow to customize separator for messaging destinations

In order to be able to use separators like "." (used by default
by most broker relays) instead of "/" for destination patterns
handling, the PathMatcher used in spring-messaging can now
be customized easily thanks to XML websocket namespace
or JavaConfig.

AntPathMatcher has been updated in order to use the configured path
separator instead of an hardcoded "/" for path concatenation.
Extension handling is now disabled when the "." separator is configured.

Issue: SPR-11660
This commit is contained in:
Sebastien Deleuze
2014-07-10 15:31:50 +02:00
committed by Rossen Stoyanchev
parent b676c41805
commit 928a466b5d
25 changed files with 967 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
/*
* 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");
* you may not use this file except in compliance with the License.
@@ -55,21 +55,35 @@ public final class DestinationPatternsMessageCondition
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
*/
public DestinationPatternsMessageCondition(String... patterns) {
this(patterns, null);
this(patterns, null, true);
}
/**
* Additional constructor with flags for using suffix pattern (.*) and
* trailing slash matches.
* Additional constructor with a customized path matcher.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param pathMatcher for path matching with patterns
* @param pathMatcher the customized path matcher to use with patterns
*/
public DestinationPatternsMessageCondition(String[] patterns,PathMatcher pathMatcher) {
this(asList(patterns), pathMatcher);
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher) {
this(asList(patterns), pathMatcher, true);
}
private DestinationPatternsMessageCondition(Collection<String> patterns, PathMatcher pathMatcher) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
/**
* 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();
}
@@ -77,13 +91,14 @@ public final class DestinationPatternsMessageCondition
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
}
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
private static Set<String> initializePatterns(Collection<String> patterns,
boolean prependLeadingSlash) {
if (patterns == null) {
return Collections.emptySet();
}
Set<String> result = new LinkedHashSet<String>(patterns.size());
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/") && prependLeadingSlash) {
pattern = "/" + pattern;
}
result.add(pattern);
@@ -134,7 +149,7 @@ public final class DestinationPatternsMessageCondition
else {
result.add("");
}
return new DestinationPatternsMessageCondition(result, this.pathMatcher);
return new DestinationPatternsMessageCondition(result, this.pathMatcher, false);
}
/**
@@ -169,7 +184,7 @@ public final class DestinationPatternsMessageCondition
}
Collections.sort(matches, this.pathMatcher.getPatternComparator(destination));
return new DestinationPatternsMessageCondition(matches, this.pathMatcher);
return new DestinationPatternsMessageCondition(matches, this.pathMatcher, false);
}
/**

View File

@@ -52,7 +52,6 @@ import org.springframework.messaging.handler.invocation.AbstractExceptionHandler
import org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.SimpAttributes;
import org.springframework.messaging.simp.SimpAttributesContextHolder;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageMappingInfo;
@@ -94,7 +93,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
private ConversionService conversionService = new DefaultFormattingConversionService();
private PathMatcher pathMatcher = new AntPathMatcher();
private PathMatcher pathMatcher;
private Validator validator;
@@ -113,7 +112,22 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
* @param brokerTemplate a messaging template to send application messages to the broker
*/
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(clientOutboundChannel, "clientOutboundChannel must not be null");
@@ -122,6 +136,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
this.clientInboundChannel = clientInboundChannel;
this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
this.brokerTemplate = brokerTemplate;
this.pathMatcher = new AntPathMatcher(pathSeparator);
Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
converters.add(new StringMessageConverter());
@@ -318,31 +333,31 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class);
if (messageAnnot != null) {
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot);
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot, typeAnnotation == null);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation).combine(result);
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
}
return result;
}
SubscribeMapping subsribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
if (subsribeAnnotation != null) {
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation);
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation, typeAnnotation == null);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation).combine(result);
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
}
return result;
}
return null;
}
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation) {
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation, boolean prependLeadingSlash) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
new DestinationPatternsMessageCondition(annotation.value()));
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
}
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) {
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation, boolean prependLeadingSlash) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
new DestinationPatternsMessageCondition(annotation.value()));
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
}
@Override

View File

@@ -29,6 +29,7 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.MessageHeaderInitializer;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.PathMatcher;
/**
* A "simple" message broker that recognizes the message types defined in
@@ -48,7 +49,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
private final SubscribableChannel brokerChannel;
private SubscriptionRegistry subscriptionRegistry = new DefaultSubscriptionRegistry();
private SubscriptionRegistry subscriptionRegistry;
private MessageHeaderInitializer headerInitializer;
@@ -63,6 +64,20 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
*/
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
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);
Assert.notNull(clientInboundChannel, "'clientInboundChannel' must not be null");
@@ -71,6 +86,11 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
this.clientInboundChannel = clientInboundChannel;
this.clientOutboundChannel = clientOutboundChannel;
this.brokerChannel = brokerChannel;
DefaultSubscriptionRegistry subscriptionRegistry = new DefaultSubscriptionRegistry();
if(pathMatcher != null) {
subscriptionRegistry.setPathMatcher(pathMatcher);
}
this.subscriptionRegistry = subscriptionRegistry;
}

View File

@@ -1,5 +1,5 @@
/*
* 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");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import java.util.List;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
import org.springframework.util.Assert;
/**
@@ -67,6 +66,4 @@ public abstract class AbstractBrokerRegistration {
return this.destinationPrefixes;
}
protected abstract AbstractBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel);
}

View File

@@ -39,6 +39,7 @@ import org.springframework.messaging.simp.user.UserSessionRegistry;
import org.springframework.messaging.support.AbstractSubscribableChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.validation.Errors;
@@ -206,12 +207,16 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
String defaultSeparator = this.getBrokerRegistry().getDefaultSeparator();
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate(), defaultSeparator);
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter());
handler.setValidator(simpValidator());
AntPathMatcher pathMatcher = new AntPathMatcher(defaultSeparator);
handler.setPathMatcher(pathMatcher);
return handler;
}

View File

@@ -23,12 +23,14 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
/**
* A registry for configuring message broker options.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.0
*/
public class MessageBrokerRegistry {
@@ -47,6 +49,7 @@ public class MessageBrokerRegistry {
private ChannelRegistration brokerChannelRegistration = new ChannelRegistration();
private String defaultSeparator;
public MessageBrokerRegistry(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
Assert.notNull(clientInboundChannel);
@@ -119,12 +122,24 @@ public class MessageBrokerRegistry {
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) {
if ((this.simpleBrokerRegistration == null) && (this.brokerRelayRegistration == null)) {
enableSimpleBroker();
}
if (this.simpleBrokerRegistration != null) {
return this.simpleBrokerRegistration.getMessageHandler(brokerChannel);
AntPathMatcher pathMatcher = new AntPathMatcher(this.defaultSeparator);
return this.simpleBrokerRegistration.getMessageHandler(brokerChannel, pathMatcher);
}
return null;
}
@@ -148,4 +163,9 @@ public class MessageBrokerRegistry {
protected ChannelRegistration getBrokerChannelRegistration() {
return this.brokerChannelRegistration;
}
protected String getDefaultSeparator() {
return this.defaultSeparator;
}
}

View File

@@ -1,5 +1,5 @@
/*
* 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");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,13 @@ package org.springframework.messaging.simp.config;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.util.PathMatcher;
/**
* Registration class for configuring a {@link SimpleBrokerMessageHandler}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.0
*/
public class SimpleBrokerRegistration extends AbstractBrokerRegistration {
@@ -36,10 +38,9 @@ public class SimpleBrokerRegistration extends AbstractBrokerRegistration {
}
@Override
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel, PathMatcher pathMatcher) {
return new SimpleBrokerMessageHandler(getClientInboundChannel(),
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes());
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes(), pathMatcher);
}
}