diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java
index 0eb766c84c..2d27db7d2b 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java
@@ -384,6 +384,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
if (prefix != null) {
resolver.setUserDestinationPrefix(prefix);
}
+ resolver.setPathMatcher(getBrokerRegistry().getPathMatcher());
return resolver;
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
index d8593c71a6..67a77f0ab8 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -29,6 +29,7 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
/**
@@ -57,6 +58,8 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
private String prefix = "/user/";
+ private boolean keepLeadingSlash = true;
+
/**
* Create an instance that will access user session id information through
@@ -94,6 +97,26 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
return this.prefix;
}
+ /**
+ * Provide the {@code PathMatcher} in use for working with destinations
+ * which in turn helps to determine whether the leading slash should be
+ * kept in actual destinations after removing the
+ * {@link #setUserDestinationPrefix userDestinationPrefix}.
+ *
By default actual destinations have a leading slash, e.g.
+ * {@code /queue/position-updates} which makes sense with brokers that
+ * support destinations with slash as separator. When a {@code PathMatcher}
+ * is provided that supports an alternative separator, then resulting
+ * destinations won't have a leading slash, e.g. {@code
+ * jms.queue.position-updates}.
+ * @param pathMatcher the PathMatcher used to work with destinations
+ * @since 4.3
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ if (pathMatcher != null) {
+ this.keepLeadingSlash = pathMatcher.combine("1", "2").equals("1/2");
+ }
+ }
+
@Override
public UserDestinationResult resolveDestination(Message> message) {
@@ -131,6 +154,9 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
}
int prefixEnd = this.prefix.length() - 1;
String actualDestination = destination.substring(prefixEnd);
+ if (!this.keepLeadingSlash) {
+ actualDestination = actualDestination.substring(1);
+ }
String user = (principal != null ? principal.getName() : null);
return new ParseResult(actualDestination, destination, Collections.singleton(sessionId), user);
}
@@ -165,6 +191,9 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
sessionIds = Collections.emptySet();
}
}
+ if (!this.keepLeadingSlash) {
+ actualDestination = actualDestination.substring(1);
+ }
return new ParseResult(actualDestination, subscribeDestination, sessionIds, userName);
}
else {
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java
index e72a77cea2..de83071c09 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.hamcrest.Matchers;
import org.junit.Test;
+import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -54,9 +55,11 @@ import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
+import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
import org.springframework.messaging.simp.user.MultiServerUserRegistry;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
+import org.springframework.messaging.simp.user.UserDestinationResolver;
import org.springframework.messaging.simp.user.UserRegistryMessageHandler;
import org.springframework.messaging.support.AbstractSubscribableChannel;
import org.springframework.messaging.support.ChannelInterceptor;
@@ -384,6 +387,10 @@ public class MessageBrokerConfigurationTests {
SimpAnnotationMethodMessageHandler handler = this.customContext.getBean(SimpAnnotationMethodMessageHandler.class);
assertEquals("a.a", handler.getPathMatcher().combine("a", "a"));
+
+ DefaultUserDestinationResolver resolver = this.customContext.getBean(DefaultUserDestinationResolver.class);
+ assertNotNull(resolver);
+ assertEquals(false, new DirectFieldAccessor(resolver).getPropertyValue("keepLeadingSlash"));
}
@Test
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
index fc12a5ba49..0a4a77942b 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -29,6 +29,8 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.TestPrincipal;
import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
/**
@@ -71,9 +73,23 @@ public class DefaultUserDestinationResolverTests {
assertEquals(user.getName(), actual.getUser());
}
- // SPR-11325
+ @Test // SPR-14044
+ public void handleSubscribeForDestinationWithoutLeadingSlash() {
+ AntPathMatcher pathMatcher = new AntPathMatcher();
+ pathMatcher.setPathSeparator(".");
+ this.resolver.setPathMatcher(pathMatcher);
- @Test
+ TestPrincipal user = new TestPrincipal("joe");
+ String destination = "/user/jms.queue.call";
+ Message> message = createMessage(SimpMessageType.SUBSCRIBE, user, "123", destination);
+ UserDestinationResult actual = this.resolver.resolveDestination(message);
+
+ assertEquals(1, actual.getTargetDestinations().size());
+ assertEquals("jms.queue.call-user123", actual.getTargetDestinations().iterator().next());
+ assertEquals(destination, actual.getSubscribeDestination());
+ }
+
+ @Test // SPR-11325
public void handleSubscribeOneUserMultipleSessions() {
TestSimpUser simpUser = new TestSimpUser("joe");
@@ -125,9 +141,23 @@ public class DefaultUserDestinationResolverTests {
assertEquals(user.getName(), actual.getUser());
}
- // SPR-12444
+ @Test // SPR-14044
+ public void handleMessageForDestinationWithDotSeparator() {
+ AntPathMatcher pathMatcher = new AntPathMatcher();
+ pathMatcher.setPathSeparator(".");
+ this.resolver.setPathMatcher(pathMatcher);
- @Test
+ TestPrincipal user = new TestPrincipal("joe");
+ String destination = "/user/joe/jms.queue.call";
+ Message> message = createMessage(SimpMessageType.MESSAGE, user, "123", destination);
+ UserDestinationResult actual = this.resolver.resolveDestination(message);
+
+ assertEquals(1, actual.getTargetDestinations().size());
+ assertEquals("jms.queue.call-user123", actual.getTargetDestinations().iterator().next());
+ assertEquals("/user/jms.queue.call", actual.getSubscribeDestination());
+ }
+
+ @Test // SPR-12444
public void handleMessageToOtherUser() {
TestSimpUser otherSimpUser = new TestSimpUser("anna");
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java
index c234f37077..3c8ebc28ce 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java
@@ -573,6 +573,10 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
if (brokerElem.hasAttribute("user-destination-prefix")) {
beanDef.getPropertyValues().add("userDestinationPrefix", brokerElem.getAttribute("user-destination-prefix"));
}
+ if (brokerElem.hasAttribute("path-matcher")) {
+ String pathMatcherRef = brokerElem.getAttribute("path-matcher");
+ beanDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
+ }
return new RuntimeBeanReference(registerBeanDef(beanDef, context, source));
}