From 183594207fbb447e1b59262b4469f2aefbb8a3ec Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 15 Mar 2016 17:46:47 -0400 Subject: [PATCH] Support user destinations without leading slash Before this commit the DefaultUserDestinationResolver did not support well broker destinations that use dot as separator with a built in assumptions that the destinations it resolves must start with slash. This change adds PathMatcher property that is used to determine if an alternative path separator is in use and if so the leading slash is left out. Issue: SPR-14044 --- .../AbstractMessageBrokerConfiguration.java | 1 + .../user/DefaultUserDestinationResolver.java | 31 +++++++++++++- .../MessageBrokerConfigurationTests.java | 9 ++++- .../DefaultUserDestinationResolverTests.java | 40 ++++++++++++++++--- .../MessageBrokerBeanDefinitionParser.java | 4 ++ 5 files changed, 78 insertions(+), 7 deletions(-) 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)); }