Allow to customize separator for messaging destinations

In order to be able to use separators like "." (used by default
by most broker relays) instead of "/" for destination patterns
handling, the PathMatcher used in spring-messaging can now
be customized easily thanks to XML websocket namespace
or JavaConfig.

AntPathMatcher has been updated in order to use the configured path
separator instead of an hardcoded "/" for path concatenation.
Extension handling is now disabled when the "." separator is configured.

Issue: SPR-11660
This commit is contained in:
Sebastien Deleuze
2014-07-10 15:31:50 +02:00
committed by Rossen Stoyanchev
parent b676c41805
commit 928a466b5d
25 changed files with 967 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@@ -18,18 +18,36 @@ package org.springframework.messaging.handler;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized.Parameter;
import org.springframework.util.AntPathMatcher;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* Unit tests for {@link DestinationPatternsMessageCondition}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
@RunWith(Parameterized.class)
public class DestinationPatternsMessageConditionTests {
@Parameter(0)
public String pathSeparator;
@Parameters
public static Iterable<Object[]> arguments() {
return Arrays.asList(new Object[][]{{"/"}, {"."}});
}
@Test
public void prependSlash() {
DestinationPatternsMessageCondition c = condition("foo");
@@ -47,20 +65,21 @@ public class DestinationPatternsMessageConditionTests {
@Test
public void combineEmptySets() {
DestinationPatternsMessageCondition c1 = condition();
DestinationPatternsMessageCondition c2 = condition();
DestinationPatternsMessageCondition c2 = suffixCondition();
assertEquals(condition(""), c1.combine(c2));
}
@Test
public void combineOnePatternWithEmptySet() {
DestinationPatternsMessageCondition c1 = condition("/type1", "/type2");
DestinationPatternsMessageCondition c2 = condition();
DestinationPatternsMessageCondition c1 = condition("/type1",
pathSeparator + "type2");
DestinationPatternsMessageCondition c2 = suffixCondition();
assertEquals(condition("/type1", "/type2"), c1.combine(c2));
assertEquals(condition("/type1", pathSeparator + "type2"), c1.combine(c2));
c1 = condition();
c2 = condition("/method1", "/method2");
c2 = suffixCondition("/method1", "/method2");
assertEquals(condition("/method1", "/method2"), c1.combine(c2));
}
@@ -68,10 +87,12 @@ public class DestinationPatternsMessageConditionTests {
@Test
public void combineMultiplePatterns() {
DestinationPatternsMessageCondition c1 = condition("/t1", "/t2");
DestinationPatternsMessageCondition c2 = condition("/m1", "/m2");
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "m1",
pathSeparator + "m2");
assertEquals(new DestinationPatternsMessageCondition(
"/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"), c1.combine(c2));
assertEquals(
condition("/t1" + pathSeparator + "m1", "/t1" + pathSeparator + "m2",
"/t2" + pathSeparator + "m1", "/t2" + pathSeparator + "m2"), c1.combine(c2));
}
@Test
@@ -84,35 +105,40 @@ public class DestinationPatternsMessageConditionTests {
@Test
public void matchPattern() {
DestinationPatternsMessageCondition condition = condition("/foo/*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo/bar"));
DestinationPatternsMessageCondition condition = condition(
"/foo" + pathSeparator + "*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo" + pathSeparator + "bar"));
assertNotNull(match);
}
@Test
public void matchSortPatterns() {
DestinationPatternsMessageCondition condition = condition("/**", "/foo/bar", "/foo/*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo/bar"));
DestinationPatternsMessageCondition expected = condition("/foo/bar", "/foo/*", "/**");
DestinationPatternsMessageCondition condition = suffixCondition(
pathSeparator + "**", pathSeparator + "foo" + pathSeparator + "bar",
pathSeparator + "foo" + pathSeparator + "*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo(pathSeparator + "foo" + pathSeparator + "bar"));
DestinationPatternsMessageCondition expected = suffixCondition(
pathSeparator + "foo" + pathSeparator + "bar",
pathSeparator + "foo" + pathSeparator + "*", pathSeparator + "**");
assertEquals(expected, match);
}
@Test
public void compareEqualPatterns() {
DestinationPatternsMessageCondition c1 = condition("/foo*");
DestinationPatternsMessageCondition c2 = condition("/foo*");
DestinationPatternsMessageCondition c1 = suffixCondition(pathSeparator + "foo*");
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "foo*");
assertEquals(0, c1.compareTo(c2, messageTo("/foo")));
assertEquals(0, c1.compareTo(c2, messageTo(pathSeparator + "foo")));
}
@Test
public void comparePatternSpecificity() {
DestinationPatternsMessageCondition c1 = condition("/fo*");
DestinationPatternsMessageCondition c2 = condition("/foo");
DestinationPatternsMessageCondition c1 = suffixCondition(pathSeparator + "fo*");
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "foo");
assertEquals(1, c1.compareTo(c2, messageTo("/foo")));
assertEquals(1, c1.compareTo(c2, messageTo(pathSeparator + "foo")));
}
@Test
@@ -130,7 +156,11 @@ public class DestinationPatternsMessageConditionTests {
private DestinationPatternsMessageCondition condition(String... patterns) {
return new DestinationPatternsMessageCondition(patterns);
return new DestinationPatternsMessageCondition(patterns, new AntPathMatcher(this.pathSeparator));
}
private DestinationPatternsMessageCondition suffixCondition(String... patterns) {
return new DestinationPatternsMessageCondition(patterns, new AntPathMatcher(this.pathSeparator), false);
}
private Message<?> messageTo(String destination) {

View File

@@ -27,6 +27,7 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -39,6 +40,7 @@ import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
import org.springframework.messaging.simp.user.UserSessionRegistry;
@@ -52,6 +54,7 @@ import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MimeTypeUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@@ -64,6 +67,7 @@ import static org.junit.Assert.*;
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sebastien Deleuze
*/
public class MessageBrokerConfigurationTests {
@@ -75,6 +79,8 @@ public class MessageBrokerConfigurationTests {
private AnnotationConfigApplicationContext customChannelContext;
private AnnotationConfigApplicationContext customMatchingContext;
@Before
public void setupOnce() {
@@ -94,6 +100,10 @@ public class MessageBrokerConfigurationTests {
this.customChannelContext = new AnnotationConfigApplicationContext();
this.customChannelContext.register(CustomChannelConfig.class);
this.customChannelContext.refresh();
this.customMatchingContext = new AnnotationConfigApplicationContext();
this.customMatchingContext.register(CustomMatchingSimpleBrokerConfig.class);
this.customMatchingContext.refresh();
}
@@ -396,6 +406,20 @@ public class MessageBrokerConfigurationTests {
assertThat(messageHandler.getValidator(), Matchers.notNullValue(Validator.class));
}
@Test
public void customMatching() {
SimpleBrokerMessageHandler brokerHandler = this.customMatchingContext.getBean(SimpleBrokerMessageHandler.class);
DefaultSubscriptionRegistry subscriptionRegistry = (DefaultSubscriptionRegistry)brokerHandler.getSubscriptionRegistry();
AntPathMatcher pathMatcher = (AntPathMatcher)subscriptionRegistry.getPathMatcher();
DirectFieldAccessor accessor = new DirectFieldAccessor(pathMatcher);
assertEquals(".", accessor.getPropertyValue("pathSeparator"));
SimpAnnotationMethodMessageHandler messageHandler = customMatchingContext.getBean(SimpAnnotationMethodMessageHandler.class);
pathMatcher = (AntPathMatcher)messageHandler.getPathMatcher();
accessor = new DirectFieldAccessor(pathMatcher);
assertEquals(".", accessor.getPropertyValue("pathSeparator"));
}
@Controller
static class TestController {
@@ -479,6 +503,15 @@ public class MessageBrokerConfigurationTests {
}
}
@Configuration
static class CustomMatchingSimpleBrokerConfig extends SimpleBrokerConfig {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.defaultSeparator(".").enableSimpleBroker("/topic", "/queue");
}
}
private static class TestChannel extends ExecutorSubscribableChannel {