Skip to content

Commit ef4b62c

Browse files
committed
Refine "." separator support for STOMP messaging
After this commit DefaultUserDestinationResolves no longer looks at whether AntPathMatcher is configured with "." as separator and rather expects to be explicitly told whether to keep the leading slash in translated destinations which actually depends on what the message broker supports (e.g. RabbitMQ "/", Artemis ".") or how it is configured (simple broker could be either way). There is also a minor improvement in SimpMessagingTemplate to ensure user destinations are correctly formed based on what the DefaultUserDestinationResolver expects. When using "." as separtor it allows sending messages to "queue.q1" rather than "/queue.q1". Issue: SPR-16275
1 parent 238e9ae commit ef4b62c

File tree

5 files changed

+173
-21
lines changed

5 files changed

+173
-21
lines changed

spring-messaging/src/main/java/org/springframework/messaging/simp/SimpMessagingTemplate.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ public void convertAndSendToUser(String user, String destination, Object payload
221221

222222
Assert.notNull(user, "User must not be null");
223223
user = StringUtils.replace(user, "/", "%2F");
224+
destination = destination.startsWith("/") ? destination : "/" + destination;
224225
super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
225226
}
226227

spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.messaging.simp.config;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collection;
2021
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
@@ -284,7 +285,18 @@ protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> retu
284285
@Bean
285286
public AbstractBrokerMessageHandler simpleBrokerMessageHandler() {
286287
SimpleBrokerMessageHandler handler = getBrokerRegistry().getSimpleBroker(brokerChannel());
287-
return (handler != null ? handler : new NoOpBrokerMessageHandler());
288+
if (handler == null) {
289+
return new NoOpBrokerMessageHandler();
290+
}
291+
updateUserDestinationResolver(handler);
292+
return handler;
293+
}
294+
295+
private void updateUserDestinationResolver(AbstractBrokerMessageHandler handler) {
296+
Collection<String> prefixes = handler.getDestinationPrefixes();
297+
if (!prefixes.isEmpty() && !prefixes.iterator().next().startsWith("/")) {
298+
((DefaultUserDestinationResolver) userDestinationResolver()).setRemoveLeadingSlash(true);
299+
}
288300
}
289301

290302
@Bean
@@ -303,6 +315,7 @@ public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() {
303315
subscriptions.put(destination, userRegistryMessageHandler());
304316
}
305317
handler.setSystemSubscriptions(subscriptions);
318+
updateUserDestinationResolver(handler);
306319
return handler;
307320
}
308321

@@ -387,7 +400,6 @@ public UserDestinationResolver userDestinationResolver() {
387400
if (prefix != null) {
388401
resolver.setUserDestinationPrefix(prefix);
389402
}
390-
resolver.setPathMatcher(getBrokerRegistry().getPathMatcher());
391403
return resolver;
392404
}
393405

spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
5858

5959
private String prefix = "/user/";
6060

61-
private boolean keepLeadingSlash = true;
61+
private boolean removeLeadingSlash = false;
6262

6363

6464
/**
@@ -97,6 +97,29 @@ public String getDestinationPrefix() {
9797
return this.prefix;
9898
}
9999

100+
/**
101+
* Use this property to indicate whether the leading slash from translated
102+
* user destinations should be removed or not. This depends on the
103+
* destination prefixes the message broker is configured with.
104+
* <p>By default this is set to {@code false}, i.e. "do not change the
105+
* target destination", although
106+
* {@link org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration
107+
* AbstractMessageBrokerConfiguration} may change that to {@code true} if
108+
* the configured destinations do not have a leading slash.
109+
* @param remove whether to remove the leading slash
110+
* @since 4.3.14
111+
*/
112+
public void setRemoveLeadingSlash(boolean remove) {
113+
this.removeLeadingSlash = remove;
114+
}
115+
116+
/**
117+
* Whether to remove the leading slash from target destinations.
118+
*/
119+
public boolean isRemoveLeadingSlash() {
120+
return this.removeLeadingSlash;
121+
}
122+
100123
/**
101124
* Provide the {@code PathMatcher} in use for working with destinations
102125
* which in turn helps to determine whether the leading slash should be
@@ -110,11 +133,14 @@ public String getDestinationPrefix() {
110133
* jms.queue.position-updates}.
111134
* @param pathMatcher the PathMatcher used to work with destinations
112135
* @since 4.3
136+
* @deprecated as of 4.3.14 this property is no longer used and is replaced
137+
* by {@link #setRemoveLeadingSlash(boolean)} that indicates more explicitly
138+
* whether to keep the leading slash which may or may not be the case
139+
* regardless of how the {@code PathMatcher} is configured.
113140
*/
141+
@Deprecated
114142
public void setPathMatcher(PathMatcher pathMatcher) {
115-
if (pathMatcher != null) {
116-
this.keepLeadingSlash = pathMatcher.combine("1", "2").equals("1/2");
117-
}
143+
// Do nothing
118144
}
119145

120146

@@ -166,7 +192,7 @@ private ParseResult parseSubscriptionMessage(Message<?> message, String sourceDe
166192
}
167193
int prefixEnd = this.prefix.length() - 1;
168194
String actualDestination = sourceDestination.substring(prefixEnd);
169-
if (!this.keepLeadingSlash) {
195+
if (isRemoveLeadingSlash()) {
170196
actualDestination = actualDestination.substring(1);
171197
}
172198
Principal principal = SimpMessageHeaderAccessor.getUser(headers);
@@ -193,7 +219,7 @@ private ParseResult parseMessage(MessageHeaders headers, String sourceDestinatio
193219
else {
194220
sessionIds = getSessionIdsByUser(userName, sessionId);
195221
}
196-
if (!this.keepLeadingSlash) {
222+
if (isRemoveLeadingSlash()) {
197223
actualDestination = actualDestination.substring(1);
198224
}
199225
return new ParseResult(sourceDestination, actualDestination, subscribeDestination,

spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
import org.hamcrest.Matchers;
2626
import org.junit.Test;
2727

28-
import org.springframework.beans.DirectFieldAccessor;
2928
import org.springframework.context.ApplicationContext;
3029
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3130
import org.springframework.context.annotation.Bean;
3231
import org.springframework.context.annotation.Configuration;
3332
import org.springframework.context.support.StaticApplicationContext;
3433
import org.springframework.messaging.Message;
34+
import org.springframework.messaging.MessageChannel;
3535
import org.springframework.messaging.MessageHandler;
3636
import org.springframework.messaging.converter.ByteArrayMessageConverter;
3737
import org.springframework.messaging.converter.CompositeMessageConverter;
@@ -44,7 +44,9 @@
4444
import org.springframework.messaging.handler.annotation.SendTo;
4545
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
4646
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
47+
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
4748
import org.springframework.messaging.simp.SimpMessageType;
49+
import org.springframework.messaging.simp.SimpMessagingTemplate;
4850
import org.springframework.messaging.simp.annotation.SubscribeMapping;
4951
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
5052
import org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry;
@@ -66,6 +68,7 @@
6668
import org.springframework.stereotype.Controller;
6769
import org.springframework.util.AntPathMatcher;
6870
import org.springframework.util.MimeTypeUtils;
71+
import org.springframework.util.PathMatcher;
6972
import org.springframework.validation.Errors;
7073
import org.springframework.validation.Validator;
7174
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
@@ -412,7 +415,7 @@ public void customPathMatcher() {
412415

413416
DefaultUserDestinationResolver resolver = context.getBean(DefaultUserDestinationResolver.class);
414417
assertNotNull(resolver);
415-
assertEquals(false, new DirectFieldAccessor(resolver).getPropertyValue("keepLeadingSlash"));
418+
assertEquals(false, resolver.isRemoveLeadingSlash());
416419
}
417420

418421
@Test
@@ -460,6 +463,67 @@ public void userBroadcastsDisabledWithSimpleBroker() {
460463
assertNotEquals(UserRegistryMessageHandler.class, messageHandler.getClass());
461464
}
462465

466+
@Test // SPR-16275
467+
public void dotSeparatorWithBrokerSlashConvention() {
468+
ApplicationContext context = loadConfig(DotSeparatorWithSlashBrokerConventionConfig.class);
469+
testDotSeparator(context, true);
470+
}
471+
472+
@Test // SPR-16275
473+
public void dotSeparatorWithBrokerDotConvention() {
474+
ApplicationContext context = loadConfig(DotSeparatorWithDotBrokerConventionConfig.class);
475+
testDotSeparator(context, false);
476+
}
477+
478+
private void testDotSeparator(ApplicationContext context, boolean expectLeadingSlash) {
479+
MessageChannel inChannel = context.getBean("clientInboundChannel", MessageChannel.class);
480+
TestChannel outChannel = context.getBean("clientOutboundChannel", TestChannel.class);
481+
MessageChannel brokerChannel = context.getBean("brokerChannel", MessageChannel.class);
482+
483+
484+
// 1. Subscribe to user destination
485+
486+
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SUBSCRIBE);
487+
headers.setSessionId("sess1");
488+
headers.setSubscriptionId("subs1");
489+
headers.setDestination("/user/queue.q1");
490+
Message<?> message = MessageBuilder.createMessage(new byte[0], headers.getMessageHeaders());
491+
inChannel.send(message);
492+
493+
// 2. Send message to user via inboundChannel
494+
495+
headers = StompHeaderAccessor.create(StompCommand.SEND);
496+
headers.setSessionId("sess1");
497+
headers.setDestination("/user/sess1/queue.q1");
498+
message = MessageBuilder.createMessage("123".getBytes(), headers.getMessageHeaders());
499+
inChannel.send(message);
500+
501+
assertEquals(1, outChannel.messages.size());
502+
Message<?> outputMessage = outChannel.messages.remove(0);
503+
headers = StompHeaderAccessor.wrap(outputMessage);
504+
505+
assertEquals(SimpMessageType.MESSAGE, headers.getMessageType());
506+
assertEquals(expectLeadingSlash ? "/queue.q1-usersess1" : "queue.q1-usersess1", headers.getDestination());
507+
assertEquals("123", new String((byte[]) outputMessage.getPayload()));
508+
509+
510+
// 3. Send message via broker channel
511+
512+
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel);
513+
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();
514+
accessor.setSessionId("sess1");
515+
template.convertAndSendToUser("sess1", "queue.q1", "456".getBytes(), accessor.getMessageHeaders());
516+
517+
assertEquals(1, outChannel.messages.size());
518+
outputMessage = outChannel.messages.remove(0);
519+
headers = StompHeaderAccessor.wrap(outputMessage);
520+
521+
assertEquals(SimpMessageType.MESSAGE, headers.getMessageType());
522+
assertEquals(expectLeadingSlash ? "/queue.q1-usersess1" : "queue.q1-usersess1", headers.getDestination());
523+
assertEquals("456", new String((byte[]) outputMessage.getPayload()));
524+
525+
}
526+
463527
private AnnotationConfigApplicationContext loadConfig(Class<?> configClass) {
464528
return new AnnotationConfigApplicationContext(configClass);
465529
}
@@ -575,6 +639,60 @@ protected void configureMessageBroker(MessageBrokerRegistry registry) {
575639
}
576640

577641

642+
@Configuration
643+
static abstract class BaseDotSeparatorConfig extends BaseTestMessageBrokerConfig {
644+
645+
@Override
646+
protected void configureMessageBroker(MessageBrokerRegistry registry) {
647+
registry.setPathMatcher(new AntPathMatcher("."));
648+
}
649+
650+
@Override
651+
@Bean
652+
public AbstractSubscribableChannel clientInboundChannel() {
653+
// synchronous
654+
return new ExecutorSubscribableChannel(null);
655+
}
656+
657+
@Override
658+
@Bean
659+
public AbstractSubscribableChannel clientOutboundChannel() {
660+
return new TestChannel();
661+
}
662+
663+
@Override
664+
@Bean
665+
public AbstractSubscribableChannel brokerChannel() {
666+
// synchronous
667+
return new ExecutorSubscribableChannel(null);
668+
}
669+
}
670+
671+
@Configuration
672+
static class DotSeparatorWithSlashBrokerConventionConfig extends BaseDotSeparatorConfig {
673+
674+
// RabbitMQ-style broker convention for STOMP destinations
675+
676+
@Override
677+
protected void configureMessageBroker(MessageBrokerRegistry registry) {
678+
super.configureMessageBroker(registry);
679+
registry.enableSimpleBroker("/topic", "/queue");
680+
}
681+
}
682+
683+
@Configuration
684+
static class DotSeparatorWithDotBrokerConventionConfig extends BaseDotSeparatorConfig {
685+
686+
// Artemis-style broker convention for STOMP destinations
687+
688+
@Override
689+
protected void configureMessageBroker(MessageBrokerRegistry registry) {
690+
super.configureMessageBroker(registry);
691+
registry.enableSimpleBroker("topic.", "queue.");
692+
}
693+
}
694+
695+
578696
private static class TestChannel extends ExecutorSubscribableChannel {
579697

580698
private final List<Message<?>> messages = new ArrayList<>();

spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.messaging.simp.user;
1818

19-
import static org.junit.Assert.*;
20-
import static org.mockito.Mockito.*;
21-
2219
import java.security.Principal;
2320

2421
import org.junit.Before;
@@ -29,9 +26,11 @@
2926
import org.springframework.messaging.simp.SimpMessageType;
3027
import org.springframework.messaging.simp.TestPrincipal;
3128
import org.springframework.messaging.support.MessageBuilder;
32-
import org.springframework.util.AntPathMatcher;
3329
import org.springframework.util.StringUtils;
3430

31+
import static org.junit.Assert.*;
32+
import static org.mockito.Mockito.*;
33+
3534
/**
3635
* Unit tests for
3736
* {@link org.springframework.messaging.simp.user.DefaultUserDestinationResolver}.
@@ -74,9 +73,7 @@ public void handleSubscribe() {
7473

7574
@Test // SPR-14044
7675
public void handleSubscribeForDestinationWithoutLeadingSlash() {
77-
AntPathMatcher pathMatcher = new AntPathMatcher();
78-
pathMatcher.setPathSeparator(".");
79-
this.resolver.setPathMatcher(pathMatcher);
76+
this.resolver.setRemoveLeadingSlash(true);
8077

8178
TestPrincipal user = new TestPrincipal("joe");
8279
String destination = "/user/jms.queue.call";
@@ -142,9 +139,7 @@ public void handleMessage() {
142139

143140
@Test // SPR-14044
144141
public void handleMessageForDestinationWithDotSeparator() {
145-
AntPathMatcher pathMatcher = new AntPathMatcher();
146-
pathMatcher.setPathSeparator(".");
147-
this.resolver.setPathMatcher(pathMatcher);
142+
this.resolver.setRemoveLeadingSlash(true);
148143

149144
TestPrincipal user = new TestPrincipal("joe");
150145
String destination = "/user/joe/jms.queue.call";

0 commit comments

Comments
 (0)