diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java index a1b39bd48f..49673a5ba8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -43,6 +43,7 @@ import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.PathMatcher; +import org.springframework.util.StringUtils; /** * Implementation of {@link SubscriptionRegistry} that stores subscriptions @@ -113,24 +114,25 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { } /** - * Configure the name of a selector header that a subscription message can - * have in order to filter messages based on their headers. The value of the - * header can use Spring EL expressions against message headers. - *

For example the following expression expects a header called "foo" to - * have the value "bar": + * Configure the name of a header that a subscription message can have for + * the purpose of filtering messages matched to the subscription. The header + * value is expected to be a Spring EL boolean expression to be applied to + * the headers of messages matched to the subscription. + *

For example: *

 	 * headers.foo == 'bar'
 	 * 
- *

By default this is set to "selector". + *

By default this is set to "selector". You can set it to a different + * name, or to {@code null} to turn off support for a selector header. + * @param selectorHeaderName the name to use for a selector header * @since 4.2 */ public void setSelectorHeaderName(String selectorHeaderName) { - Assert.notNull(selectorHeaderName, "'selectorHeaderName' must not be null"); - this.selectorHeaderName = selectorHeaderName; + this.selectorHeaderName = StringUtils.hasText(selectorHeaderName) ? selectorHeaderName : null; } /** - * Return the name for the selector header. + * Return the name for the selector header name. * @since 4.2 */ public String getSelectorHeaderName() { @@ -142,25 +144,31 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { protected void addSubscriptionInternal( String sessionId, String subsId, String destination, Message message) { + Expression expression = getSelectorExpression(message.getHeaders()); + this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression); + this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId); + } + + private Expression getSelectorExpression(MessageHeaders headers) { Expression expression = null; - MessageHeaders headers = message.getHeaders(); - String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers); - if (selector != null) { - try { - expression = this.expressionParser.parseExpression(selector); - this.selectorHeaderInUse = true; - if (logger.isTraceEnabled()) { - logger.trace("Subscription selector: [" + selector + "]"); + if (getSelectorHeaderName() != null) { + String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers); + if (selector != null) { + try { + expression = this.expressionParser.parseExpression(selector); + this.selectorHeaderInUse = true; + if (logger.isTraceEnabled()) { + logger.trace("Subscription selector: [" + selector + "]"); + } } - } - catch (Throwable ex) { - if (logger.isDebugEnabled()) { - logger.debug("Failed to parse selector: " + selector, ex); + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to parse selector: " + selector, ex); + } } } } - this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression); - this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId); + return expression; } @Override diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java index 5916e0ccf3..021042b5e6 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -51,23 +51,27 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler { private static final byte[] EMPTY_PAYLOAD = new byte[0]; - private final Map sessions = new ConcurrentHashMap(); - - private SubscriptionRegistry subscriptionRegistry; private PathMatcher pathMatcher; private Integer cacheLimit; + private String selectorHeaderName = "selector"; + private TaskScheduler taskScheduler; private long[] heartbeatValue; - private ScheduledFuture heartbeatFuture; - private MessageHeaderInitializer headerInitializer; + private SubscriptionRegistry subscriptionRegistry; + + private final Map sessions = new ConcurrentHashMap(); + + private ScheduledFuture heartbeatFuture; + + /** * Create a SimpleBrokerMessageHandler instance with the given message channels * and destination prefixes. @@ -96,12 +100,40 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler { this.subscriptionRegistry = subscriptionRegistry; initPathMatcherToUse(); initCacheLimitToUse(); + initSelectorHeaderNameToUse(); } public SubscriptionRegistry getSubscriptionRegistry() { return this.subscriptionRegistry; } + /** + * Configure the name of a header that a subscription message can have for + * the purpose of filtering messages matched to the subscription. The header + * value is expected to be a Spring EL boolean expression to be applied to + * the headers of messages matched to the subscription. + *

For example: + *

+	 * headers.foo == 'bar'
+	 * 
+ *

By default this is set to "selector". You can set it to a different + * name, or to {@code null} to turn off support for a selector header. + * @param selectorHeaderName the name to use for a selector header + * @since 4.3.17 + * @see #setSubscriptionRegistry + * @see DefaultSubscriptionRegistry#setSelectorHeaderName(String) + */ + public void setSelectorHeaderName(String selectorHeaderName) { + this.selectorHeaderName = selectorHeaderName; + initSelectorHeaderNameToUse(); + } + + private void initSelectorHeaderNameToUse() { + if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) { + ((DefaultSubscriptionRegistry) this.subscriptionRegistry).setSelectorHeaderName(this.selectorHeaderName); + } + } + /** * When configured, the given PathMatcher is passed down to the underlying * SubscriptionRegistry to use for matching destination to subscriptions. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/SimpleBrokerRegistration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/SimpleBrokerRegistration.java index ab240affc0..93125cf0f3 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/SimpleBrokerRegistration.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/SimpleBrokerRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -33,6 +33,8 @@ public class SimpleBrokerRegistration extends AbstractBrokerRegistration { private long[] heartbeat; + private String selectorHeaderName = "selector"; + public SimpleBrokerRegistration(SubscribableChannel inChannel, MessageChannel outChannel, String[] prefixes) { super(inChannel, outChannel, prefixes); @@ -65,6 +67,24 @@ public class SimpleBrokerRegistration extends AbstractBrokerRegistration { return this; } + /** + * Configure the name of a header that a subscription message can have for + * the purpose of filtering messages matched to the subscription. The header + * value is expected to be a Spring EL boolean expression to be applied to + * the headers of messages matched to the subscription. + *

For example: + *

+	 * headers.foo == 'bar'
+	 * 
+ *

By default this is set to "selector". You can set it to a different + * name, or to {@code null} to turn off support for a selector header. + * @param selectorHeaderName the name to use for a selector header + * @since 4.3.17 + */ + public void setSelectorHeaderName(String selectorHeaderName) { + this.selectorHeaderName = selectorHeaderName; + } + @Override protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) { @@ -76,6 +96,7 @@ public class SimpleBrokerRegistration extends AbstractBrokerRegistration { if (this.heartbeat != null) { handler.setHeartbeatValue(this.heartbeat); } + handler.setSelectorHeaderName(this.selectorHeaderName); return handler; } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java index e065a68d5a..1f7fec7eba 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java @@ -264,6 +264,8 @@ public class DefaultSubscriptionRegistryTests { String destination = "/foo"; String selector = "headers.foo == 'bar'"; + // First, try with selector header + this.registry.registerSubscription(subscribeMessage(sessionId, subscriptionId, destination, selector)); SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(); @@ -276,11 +278,34 @@ public class DefaultSubscriptionRegistryTests { assertEquals(1, actual.size()); assertEquals(Collections.singletonList(subscriptionId), actual.get(sessionId)); + // Then without + actual = this.registry.findSubscriptions(createMessage(destination)); assertNotNull(actual); assertEquals(0, actual.size()); } + @Test + public void registerSubscriptionWithSelectorNotSupported() { + String sessionId = "sess01"; + String subscriptionId = "subs01"; + String destination = "/foo"; + String selector = "headers.foo == 'bar'"; + + this.registry.setSelectorHeaderName(null); + this.registry.registerSubscription(subscribeMessage(sessionId, subscriptionId, destination, selector)); + + SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(); + accessor.setDestination(destination); + accessor.setNativeHeader("foo", "bazz"); + Message message = MessageBuilder.createMessage("", accessor.getMessageHeaders()); + + MultiValueMap actual = this.registry.findSubscriptions(message); + assertNotNull(actual); + assertEquals(1, actual.size()); + assertEquals(Collections.singletonList(subscriptionId), actual.get(sessionId)); + } + @Test // SPR-11931 public void registerSubscriptionTwiceAndUnregister() { this.registry.registerSubscription(subscribeMessage("sess01", "subs01", "/foo")); 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 b12e460e94..84069c1734 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -381,6 +381,10 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser { String heartbeatValue = simpleBrokerElem.getAttribute("heartbeat"); brokerDef.getPropertyValues().add("heartbeatValue", heartbeatValue); } + if (simpleBrokerElem.hasAttribute("selector-header")) { + String headerName = simpleBrokerElem.getAttribute("selector-header"); + brokerDef.getPropertyValues().add("selectorHeaderName", headerName); + } } else if (brokerRelayElem != null) { String prefix = brokerRelayElem.getAttribute("prefix"); diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd index 10348c4ab0..2a34edbfc7 100644 --- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd +++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd @@ -384,6 +384,22 @@ ]]> + + + + + diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java index 4bff45000a..38badb53ce 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -45,6 +45,7 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentRes import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; import org.springframework.messaging.simp.SimpMessagingTemplate; 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.stomp.StompBrokerRelayMessageHandler; import org.springframework.messaging.simp.user.DefaultUserDestinationResolver; @@ -199,6 +200,8 @@ public class MessageBrokerBeanDefinitionParserTests { assertNotNull(brokerMessageHandler); Collection prefixes = brokerMessageHandler.getDestinationPrefixes(); assertEquals(Arrays.asList("/topic", "/queue"), new ArrayList(prefixes)); + DefaultSubscriptionRegistry registry = (DefaultSubscriptionRegistry) brokerMessageHandler.getSubscriptionRegistry(); + assertEquals("my-selector", registry.getSelectorHeaderName()); assertNotNull(brokerMessageHandler.getTaskScheduler()); assertArrayEquals(new long[] {15000, 15000}, brokerMessageHandler.getHeartbeatValue()); diff --git a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml index ed9baf5c09..7b3cd93f2a 100644 --- a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml +++ b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml @@ -35,7 +35,8 @@ - +