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)); }