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
This commit is contained in:
Rossen Stoyanchev
2018-01-19 15:32:38 -05:00
parent 238e9ae59f
commit ef4b62caa0
5 changed files with 173 additions and 21 deletions

View File

@@ -25,13 +25,13 @@ 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;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
@@ -44,7 +44,9 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry;
@@ -66,6 +68,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
@@ -412,7 +415,7 @@ public class MessageBrokerConfigurationTests {
DefaultUserDestinationResolver resolver = context.getBean(DefaultUserDestinationResolver.class);
assertNotNull(resolver);
assertEquals(false, new DirectFieldAccessor(resolver).getPropertyValue("keepLeadingSlash"));
assertEquals(false, resolver.isRemoveLeadingSlash());
}
@Test
@@ -460,6 +463,67 @@ public class MessageBrokerConfigurationTests {
assertNotEquals(UserRegistryMessageHandler.class, messageHandler.getClass());
}
@Test // SPR-16275
public void dotSeparatorWithBrokerSlashConvention() {
ApplicationContext context = loadConfig(DotSeparatorWithSlashBrokerConventionConfig.class);
testDotSeparator(context, true);
}
@Test // SPR-16275
public void dotSeparatorWithBrokerDotConvention() {
ApplicationContext context = loadConfig(DotSeparatorWithDotBrokerConventionConfig.class);
testDotSeparator(context, false);
}
private void testDotSeparator(ApplicationContext context, boolean expectLeadingSlash) {
MessageChannel inChannel = context.getBean("clientInboundChannel", MessageChannel.class);
TestChannel outChannel = context.getBean("clientOutboundChannel", TestChannel.class);
MessageChannel brokerChannel = context.getBean("brokerChannel", MessageChannel.class);
// 1. Subscribe to user destination
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SUBSCRIBE);
headers.setSessionId("sess1");
headers.setSubscriptionId("subs1");
headers.setDestination("/user/queue.q1");
Message<?> message = MessageBuilder.createMessage(new byte[0], headers.getMessageHeaders());
inChannel.send(message);
// 2. Send message to user via inboundChannel
headers = StompHeaderAccessor.create(StompCommand.SEND);
headers.setSessionId("sess1");
headers.setDestination("/user/sess1/queue.q1");
message = MessageBuilder.createMessage("123".getBytes(), headers.getMessageHeaders());
inChannel.send(message);
assertEquals(1, outChannel.messages.size());
Message<?> outputMessage = outChannel.messages.remove(0);
headers = StompHeaderAccessor.wrap(outputMessage);
assertEquals(SimpMessageType.MESSAGE, headers.getMessageType());
assertEquals(expectLeadingSlash ? "/queue.q1-usersess1" : "queue.q1-usersess1", headers.getDestination());
assertEquals("123", new String((byte[]) outputMessage.getPayload()));
// 3. Send message via broker channel
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel);
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();
accessor.setSessionId("sess1");
template.convertAndSendToUser("sess1", "queue.q1", "456".getBytes(), accessor.getMessageHeaders());
assertEquals(1, outChannel.messages.size());
outputMessage = outChannel.messages.remove(0);
headers = StompHeaderAccessor.wrap(outputMessage);
assertEquals(SimpMessageType.MESSAGE, headers.getMessageType());
assertEquals(expectLeadingSlash ? "/queue.q1-usersess1" : "queue.q1-usersess1", headers.getDestination());
assertEquals("456", new String((byte[]) outputMessage.getPayload()));
}
private AnnotationConfigApplicationContext loadConfig(Class<?> configClass) {
return new AnnotationConfigApplicationContext(configClass);
}
@@ -575,6 +639,60 @@ public class MessageBrokerConfigurationTests {
}
@Configuration
static abstract class BaseDotSeparatorConfig extends BaseTestMessageBrokerConfig {
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setPathMatcher(new AntPathMatcher("."));
}
@Override
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
// synchronous
return new ExecutorSubscribableChannel(null);
}
@Override
@Bean
public AbstractSubscribableChannel clientOutboundChannel() {
return new TestChannel();
}
@Override
@Bean
public AbstractSubscribableChannel brokerChannel() {
// synchronous
return new ExecutorSubscribableChannel(null);
}
}
@Configuration
static class DotSeparatorWithSlashBrokerConventionConfig extends BaseDotSeparatorConfig {
// RabbitMQ-style broker convention for STOMP destinations
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
super.configureMessageBroker(registry);
registry.enableSimpleBroker("/topic", "/queue");
}
}
@Configuration
static class DotSeparatorWithDotBrokerConventionConfig extends BaseDotSeparatorConfig {
// Artemis-style broker convention for STOMP destinations
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
super.configureMessageBroker(registry);
registry.enableSimpleBroker("topic.", "queue.");
}
}
private static class TestChannel extends ExecutorSubscribableChannel {
private final List<Message<?>> messages = new ArrayList<>();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -16,9 +16,6 @@
package org.springframework.messaging.simp.user;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.security.Principal;
import org.junit.Before;
@@ -29,9 +26,11 @@ 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.StringUtils;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for
* {@link org.springframework.messaging.simp.user.DefaultUserDestinationResolver}.
@@ -74,9 +73,7 @@ public class DefaultUserDestinationResolverTests {
@Test // SPR-14044
public void handleSubscribeForDestinationWithoutLeadingSlash() {
AntPathMatcher pathMatcher = new AntPathMatcher();
pathMatcher.setPathSeparator(".");
this.resolver.setPathMatcher(pathMatcher);
this.resolver.setRemoveLeadingSlash(true);
TestPrincipal user = new TestPrincipal("joe");
String destination = "/user/jms.queue.call";
@@ -142,9 +139,7 @@ public class DefaultUserDestinationResolverTests {
@Test // SPR-14044
public void handleMessageForDestinationWithDotSeparator() {
AntPathMatcher pathMatcher = new AntPathMatcher();
pathMatcher.setPathSeparator(".");
this.resolver.setPathMatcher(pathMatcher);
this.resolver.setRemoveLeadingSlash(true);
TestPrincipal user = new TestPrincipal("joe");
String destination = "/user/joe/jms.queue.call";