Refactor SubscriptionRegistry

The SubscriptionRegistry and implementations are now in a package
together with SimpleBrokerWebMessageHandler and primarily support
with matching subscriptions to messages. Subscriptions can contain
patterns as supported by AntPathMatcher.

StopmWebSocketHandler no longer keeps track of subscriptions and simply
ignores messages without a subscription id, since it has no way of
knowing broker-specific destination semantics for patterns.
This commit is contained in:
Rossen Stoyanchev
2013-07-07 14:18:58 -04:00
parent f25ccac1a1
commit 3a2f5e71b7
21 changed files with 688 additions and 836 deletions

View File

@@ -28,6 +28,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.messaging.MessageType;
import org.springframework.web.messaging.service.broker.SimpleBrokerWebMessageHandler;
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
import static org.junit.Assert.*;
@@ -89,29 +90,29 @@ public class SimpleBrokerWebMessageHandlerTests {
@Test
public void subcribeDisconnectPublish() {
this.messageHandler.handleSubscribe(createSubscriptionMessage("sess1", "sub1", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage("sess1", "sub2", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage("sess1", "sub3", "/bar"));
String sess1 = "sess1";
String sess2 = "sess2";
this.messageHandler.handleSubscribe(createSubscriptionMessage("sess2", "sub1", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage("sess2", "sub2", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage("sess2", "sub3", "/bar"));
this.messageHandler.handleSubscribe(createSubscriptionMessage(sess1, "sub1", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage(sess1, "sub2", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage(sess1, "sub3", "/bar"));
this.messageHandler.handleSubscribe(createSubscriptionMessage(sess2, "sub1", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage(sess2, "sub2", "/foo"));
this.messageHandler.handleSubscribe(createSubscriptionMessage(sess2, "sub3", "/bar"));
WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.create(MessageType.DISCONNECT);
headers.setSessionId("sess1");
headers.setSessionId(sess1);
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toMap()).build();
this.messageHandler.handleDisconnect(message);
this.messageHandler.handlePublish(createMessage("/foo", "message1"));
this.messageHandler.handlePublish(createMessage("/bar", "message2"));
verify(this.clientChannel, times(6)).send(this.messageCaptor.capture());
assertCapturedMessage("sess1", "sub1", "/foo");
assertCapturedMessage("sess1", "sub2", "/foo");
assertCapturedMessage("sess2", "sub1", "/foo");
assertCapturedMessage("sess2", "sub2", "/foo");
assertCapturedMessage("sess1", "sub3", "/bar");
assertCapturedMessage("sess2", "sub3", "/bar");
verify(this.clientChannel, times(3)).send(this.messageCaptor.capture());
assertCapturedMessage(sess2, "sub1", "/foo");
assertCapturedMessage(sess2, "sub2", "/foo");
assertCapturedMessage(sess2, "sub3", "/bar");
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2002-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.messaging.service.broker;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.MultiValueMap;
import org.springframework.web.messaging.MessageType;
import org.springframework.web.messaging.support.WebMessageHeaderAccesssor;
import static org.junit.Assert.*;
/**
* Test fixture for {@link DefaultSubscriptionRegistry}.
*
* @author Rossen Stoyanchev
*/
public class DefaultSubscriptionRegistryTests {
private DefaultSubscriptionRegistry registry;
@Before
public void setup() {
this.registry = new DefaultSubscriptionRegistry();
}
@Test
public void addSubscriptionInvalidInput() {
String sessId = "sess01";
String subsId = "subs01";
String dest = "/foo";
this.registry.addSubscription(subscribeMessage(null, subsId, dest));
assertEquals(0, this.registry.findSubscriptions(message(dest)).size());
this.registry.addSubscription(subscribeMessage(sessId, null, dest));
assertEquals(0, this.registry.findSubscriptions(message(dest)).size());
this.registry.addSubscription(subscribeMessage(sessId, subsId, null));
assertEquals(0, this.registry.findSubscriptions(message(dest)).size());
}
@Test
public void addSubscription() {
String sessId = "sess01";
String subsId = "subs01";
String dest = "/foo";
this.registry.addSubscription(subscribeMessage(sessId, subsId, dest));
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected one element " + actual, 1, actual.size());
assertEquals(Arrays.asList(subsId), actual.get(sessId));
}
@Test
public void addSubscriptionOneSession() {
String sessId = "sess01";
List<String> subscriptionIds = Arrays.asList("subs01", "subs02", "subs03");
String dest = "/foo";
for (String subId : subscriptionIds) {
this.registry.addSubscription(subscribeMessage(sessId, subId, dest));
}
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected one element " + actual, 1, actual.size());
assertEquals(subscriptionIds, sort(actual.get(sessId)));
}
@Test
public void addSubscriptionMultipleSessions() {
List<String> sessIds = Arrays.asList("sess01", "sess02", "sess03");
List<String> subscriptionIds = Arrays.asList("subs01", "subs02", "subs03");
String dest = "/foo";
for (String sessId : sessIds) {
for (String subsId : subscriptionIds) {
this.registry.addSubscription(subscribeMessage(sessId, subsId, dest));
}
}
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected three elements " + actual, 3, actual.size());
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(0))));
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(1))));
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(2))));
}
@Test
public void addSubscriptionWithDestinationPattern() {
String sessId = "sess01";
String subsId = "subs01";
String destPattern = "/topic/PRICE.STOCK.*.IBM";
String dest = "/topic/PRICE.STOCK.NASDAQ.IBM";
this.registry.addSubscription(subscribeMessage(sessId, subsId, destPattern));
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected one element " + actual, 1, actual.size());
assertEquals(Arrays.asList(subsId), actual.get(sessId));
}
@Test
public void addSubscriptionWithDestinationPatternRegex() {
String sessId = "sess01";
String subsId = "subs01";
String destPattern = "/topic/PRICE.STOCK.*.{ticker:(IBM|MSFT)}";
this.registry.addSubscription(subscribeMessage(sessId, subsId, destPattern));
Message<?> message = message("/topic/PRICE.STOCK.NASDAQ.IBM");
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message);
assertEquals("Expected one element " + actual, 1, actual.size());
assertEquals(Arrays.asList(subsId), actual.get(sessId));
message = message("/topic/PRICE.STOCK.NASDAQ.MSFT");
actual = this.registry.findSubscriptions(message);
assertEquals("Expected one element " + actual, 1, actual.size());
assertEquals(Arrays.asList(subsId), actual.get(sessId));
message = message("/topic/PRICE.STOCK.NASDAQ.VMW");
actual = this.registry.findSubscriptions(message);
assertEquals("Expected no elements " + actual, 0, actual.size());
}
@Test
public void removeSubscription() {
List<String> sessIds = Arrays.asList("sess01", "sess02", "sess03");
List<String> subscriptionIds = Arrays.asList("subs01", "subs02", "subs03");
String dest = "/foo";
for (String sessId : sessIds) {
for (String subsId : subscriptionIds) {
this.registry.addSubscription(subscribeMessage(sessId, subsId, dest));
}
}
this.registry.removeSubscription(unsubscribeMessage(sessIds.get(0), subscriptionIds.get(0)));
this.registry.removeSubscription(unsubscribeMessage(sessIds.get(0), subscriptionIds.get(1)));
this.registry.removeSubscription(unsubscribeMessage(sessIds.get(0), subscriptionIds.get(2)));
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected three elements " + actual, 2, actual.size());
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(1))));
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(2))));
}
@Test
public void removeSessionSubscriptions() {
List<String> sessIds = Arrays.asList("sess01", "sess02", "sess03");
List<String> subscriptionIds = Arrays.asList("subs01", "subs02", "subs03");
String dest = "/foo";
for (String sessId : sessIds) {
for (String subsId : subscriptionIds) {
this.registry.addSubscription(subscribeMessage(sessId, subsId, dest));
}
}
this.registry.removeSessionSubscriptions(sessIds.get(0));
this.registry.removeSessionSubscriptions(sessIds.get(1));
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected three elements " + actual, 1, actual.size());
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(2))));
}
@Test
public void findSubscriptionsNoMatches() {
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message("/foo"));
assertEquals("Expected no elements " + actual, 0, actual.size());
}
private Message<?> subscribeMessage(String sessionId, String subscriptionId, String destination) {
WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.create(MessageType.SUBSCRIBE);
headers.setSessionId(sessionId);
headers.setSubscriptionId(subscriptionId);
if (destination != null) {
headers.setDestination(destination);
}
return MessageBuilder.withPayload("").copyHeaders(headers.toMap()).build();
}
private Message<?> unsubscribeMessage(String sessionId, String subscriptionId) {
WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.create(MessageType.UNSUBSCRIBE);
headers.setSessionId(sessionId);
headers.setSubscriptionId(subscriptionId);
return MessageBuilder.withPayload("").copyHeaders(headers.toMap()).build();
}
private Message<?> message(String destination) {
WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.create();
headers.setDestination(destination);
return MessageBuilder.withPayload("").copyHeaders(headers.toMap()).build();
}
private List<String> sort(List<String> list) {
Collections.sort(list);
return list;
}
}

View File

@@ -1,116 +0,0 @@
/*
* Copyright 2002-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.messaging.support;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.web.messaging.SessionSubscriptionRegistration;
import org.springframework.web.messaging.SessionSubscriptionRegistry;
import static org.junit.Assert.*;
/**
* A test fixture for {@link AbstractSessionSubscriptionRegistry}.
*
* @author Rossen Stoyanchev
*/
public abstract class AbstractSessionSubscriptionRegistryTests {
protected SessionSubscriptionRegistry registry;
@Before
public void setup() {
this.registry = createSessionSubscriptionRegistry();
}
protected abstract SessionSubscriptionRegistry createSessionSubscriptionRegistry();
@Test
public void getRegistration() {
String sessionId = "sess1";
assertNull(this.registry.getRegistration(sessionId));
this.registry.getOrCreateRegistration(sessionId);
assertNotNull(this.registry.getRegistration(sessionId));
assertEquals(sessionId, this.registry.getRegistration(sessionId).getSessionId());
}
@Test
public void getOrCreateRegistration() {
String sessionId = "sess1";
assertNull(this.registry.getRegistration(sessionId));
SessionSubscriptionRegistration registration = this.registry.getOrCreateRegistration(sessionId);
assertEquals(registration, this.registry.getOrCreateRegistration(sessionId));
}
@Test
public void removeRegistration() {
String sessionId = "sess1";
this.registry.getOrCreateRegistration(sessionId);
assertNotNull(this.registry.getRegistration(sessionId));
assertEquals(sessionId, this.registry.getRegistration(sessionId).getSessionId());
this.registry.removeRegistration(sessionId);
assertNull(this.registry.getRegistration(sessionId));
}
@Test
public void getSessionSubscriptions() {
String sessionId = "sess1";
SessionSubscriptionRegistration registration = this.registry.getOrCreateRegistration(sessionId);
registration.addSubscription("/foo", "sub1");
registration.addSubscription("/foo", "sub2");
Set<String> subscriptions = this.registry.getSessionSubscriptions(sessionId, "/foo");
assertEquals("Wrong number of subscriptions " + subscriptions, 2, subscriptions.size());
assertTrue(subscriptions.contains("sub1"));
assertTrue(subscriptions.contains("sub2"));
}
@Test
public void getRegistrationsByDestination() {
SessionSubscriptionRegistration reg1 = this.registry.getOrCreateRegistration("sess1");
reg1.addSubscription("/foo", "sub1");
SessionSubscriptionRegistration reg2 = this.registry.getOrCreateRegistration("sess2");
reg2.addSubscription("/foo", "sub1");
Set<SessionSubscriptionRegistration> actual = this.registry.getRegistrationsByDestination("/foo");
assertEquals(2, actual.size());
assertTrue(actual.contains(reg1));
assertTrue(actual.contains(reg2));
reg1.removeSubscription("sub1");
actual = this.registry.getRegistrationsByDestination("/foo");
assertEquals("Invalid set of registrations " + actual, 1, actual.size());
assertTrue(actual.contains(reg2));
reg2.removeSubscription("sub1");
actual = this.registry.getRegistrationsByDestination("/foo");
assertNull("Unexpected registrations " + actual, actual);
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2002-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.messaging.support;
import org.springframework.web.messaging.SessionSubscriptionRegistry;
/**
* Test fixture for {@link CachingSessionSubscriptionRegistry}.
*
* @author Rossen Stoyanchev
*/
public class CachingSessionSubscriptionRegistryTests extends AbstractSessionSubscriptionRegistryTests {
@Override
protected SessionSubscriptionRegistry createSessionSubscriptionRegistry() {
return new CachingSessionSubscriptionRegistry(new DefaultSessionSubscriptionRegistry());
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2002-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.messaging.support;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test fixture for {@link DefaultSessionSubscriptionRegistration}.
*
* @author Rossen Stoyanchev
*/
public class DefaultSessionSubscriptionRegistrationTests {
private DefaultSessionSubscriptionRegistration registration;
@Before
public void setup() {
this.registration = new DefaultSessionSubscriptionRegistration("sess1");
}
@Test
public void addSubscriptions() {
this.registration.addSubscription("/foo", "sub1");
this.registration.addSubscription("/foo", "sub2");
this.registration.addSubscription("/bar", "sub3");
this.registration.addSubscription("/bar", "sub4");
assertSet(this.registration.getSubscriptionsByDestination("/foo"), 2, "sub1", "sub2");
assertSet(this.registration.getSubscriptionsByDestination("/bar"), 2, "sub3", "sub4");
assertSet(this.registration.getDestinations(), 2, "/foo", "/bar");
}
@Test
public void removeSubscriptions() {
this.registration.addSubscription("/foo", "sub1");
this.registration.addSubscription("/foo", "sub2");
this.registration.addSubscription("/bar", "sub3");
this.registration.addSubscription("/bar", "sub4");
assertEquals("/foo", this.registration.removeSubscription("sub1"));
assertEquals("/foo", this.registration.removeSubscription("sub2"));
assertNull(this.registration.getSubscriptionsByDestination("/foo"));
assertSet(this.registration.getDestinations(), 1, "/bar");
assertEquals("/bar", this.registration.removeSubscription("sub3"));
assertEquals("/bar", this.registration.removeSubscription("sub4"));
assertNull(this.registration.getSubscriptionsByDestination("/bar"));
assertSet(this.registration.getDestinations(), 0);
}
private void assertSet(Set<String> set, int size, String... elements) {
assertEquals("Wrong number of elements in " + set, size, set.size());
for (String element : elements) {
assertTrue("Set does not contain element " + element, set.contains(element));
}
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2002-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.messaging.support;
import org.springframework.web.messaging.SessionSubscriptionRegistry;
/**
* Test fixture for {@link DefaultSessionSubscriptionRegistry}.
*
* @author Rossen Stoyanchev
*/
public class DefaultSessionSubscriptionRegistryTests extends AbstractSessionSubscriptionRegistryTests {
@Override
protected SessionSubscriptionRegistry createSessionSubscriptionRegistry() {
return new DefaultSessionSubscriptionRegistry();
}
}