Add XML namespace for WebSocket config

This commit adds an XML namespace equivalent of @EnableWebSocket and
@EnableWebSocketMessageBroker. Those are <websocket:handlers> and
<websocket:message-broker> respectively.

Examples can be found in the test suite.

This commit also alters the way MessageHandler's subscribe to their
respective MessageChannel's of interest. Rather than performing the
subscriptions in configuration code, the message channels are now
passed into MessageHandler's so they can subscribe themselves on
startup.

Issue: SPR-11063
This commit is contained in:
Brian Clozel
2013-11-26 20:04:57 +01:00
committed by Rossen Stoyanchev
parent 8f1fefc159
commit 10f5d96a78
44 changed files with 2434 additions and 171 deletions

View File

@@ -24,10 +24,12 @@ import java.util.List;
*
* @author Rossen Stoyanchev
*/
public class StubMessageChannel implements MessageChannel {
public class StubMessageChannel implements SubscribableChannel {
private final List<Message<byte[]>> messages = new ArrayList<>();
private final List<MessageHandler> handlers = new ArrayList<>();
public List<Message<byte[]>> getMessages() {
return this.messages;
@@ -47,4 +49,15 @@ public class StubMessageChannel implements MessageChannel {
return true;
}
@Override
public boolean subscribe(MessageHandler handler) {
this.handlers.add(handler);
return true;
}
@Override
public boolean unsubscribe(MessageHandler handler) {
this.handlers.remove(handler);
return true;
}
}

View File

@@ -46,7 +46,6 @@ import org.springframework.stereotype.Controller;
import org.springframework.util.MimeTypeUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
@@ -319,6 +318,7 @@ public class MessageBrokerConfigurationTests {
}
@Override
@Bean
public AbstractSubscribableChannel brokerChannel() {
return new TestChannel();
}
@@ -334,7 +334,7 @@ public class MessageBrokerConfigurationTests {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue").setAutoStartup(false);
registry.enableStompBrokerRelay("/topic", "/queue").setAutoStartup(true);
}
}

View File

@@ -25,6 +25,7 @@ import org.mockito.Mockito;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.MessageMapping;
@@ -53,9 +54,9 @@ public class SimpAnnotationMethodMessageHandlerTests {
@Before
public void setup() {
MessageChannel channel = Mockito.mock(MessageChannel.class);
SubscribableChannel channel = Mockito.mock(SubscribableChannel.class);
SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
this.messageHandler = new TestSimpAnnotationMethodMessageHandler(brokerTemplate, channel);
this.messageHandler = new TestSimpAnnotationMethodMessageHandler(brokerTemplate, channel, channel);
this.messageHandler.setApplicationContext(new StaticApplicationContext());
this.messageHandler.afterPropertiesSet();
@@ -145,9 +146,9 @@ public class SimpAnnotationMethodMessageHandlerTests {
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
public TestSimpAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate,
MessageChannel clientOutboundChannel) {
SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
super(brokerTemplate, clientOutboundChannel);
super(clientInboundChannel, clientOutboundChannel, brokerTemplate);
}
public void registerHandler(Object handler) {

View File

@@ -26,6 +26,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
@@ -43,7 +44,13 @@ public class SimpleBrokerMessageHandlerTests {
private SimpleBrokerMessageHandler messageHandler;
@Mock
private MessageChannel clientChannel;
private SubscribableChannel clientInboundChannel;
@Mock
private MessageChannel clientOutboundChannel;
@Mock
private SubscribableChannel brokerChannel;
@Captor
ArgumentCaptor<Message<?>> messageCaptor;
@@ -52,7 +59,8 @@ public class SimpleBrokerMessageHandlerTests {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.messageHandler = new SimpleBrokerMessageHandler(this.clientChannel, Collections.<String>emptyList());
this.messageHandler = new SimpleBrokerMessageHandler(this.clientInboundChannel,
this.clientOutboundChannel, this.brokerChannel, Collections.<String>emptyList());
}
@@ -72,7 +80,7 @@ public class SimpleBrokerMessageHandlerTests {
this.messageHandler.handleMessage(createMessage("/foo", "message1"));
this.messageHandler.handleMessage(createMessage("/bar", "message2"));
verify(this.clientChannel, times(6)).send(this.messageCaptor.capture());
verify(this.clientOutboundChannel, times(6)).send(this.messageCaptor.capture());
assertCapturedMessage("sess1", "sub1", "/foo");
assertCapturedMessage("sess1", "sub2", "/foo");
assertCapturedMessage("sess2", "sub1", "/foo");
@@ -105,7 +113,7 @@ public class SimpleBrokerMessageHandlerTests {
this.messageHandler.handleMessage(createMessage("/foo", "message1"));
this.messageHandler.handleMessage(createMessage("/bar", "message2"));
verify(this.clientChannel, times(3)).send(this.messageCaptor.capture());
verify(this.clientOutboundChannel, times(3)).send(this.messageCaptor.capture());
assertCapturedMessage(sess2, "sub1", "/foo");
assertCapturedMessage(sess2, "sub2", "/foo");
assertCapturedMessage(sess2, "sub3", "/bar");
@@ -121,7 +129,7 @@ public class SimpleBrokerMessageHandlerTests {
Message<String> connectMessage = createConnectMessage(sess1);
this.messageHandler.handleMessage(connectMessage);
verify(this.clientChannel, times(1)).send(this.messageCaptor.capture());
verify(this.clientOutboundChannel, times(1)).send(this.messageCaptor.capture());
Message<?> connectAckMessage = this.messageCaptor.getValue();
SimpMessageHeaderAccessor connectAckHeaders = SimpMessageHeaderAccessor.wrap(connectAckMessage);

View File

@@ -16,21 +16,22 @@
package org.springframework.messaging.simp.handler;
import org.apache.activemq.transport.stomp.Stomp;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.messaging.Message;
import org.springframework.messaging.core.MessageSendingOperations;
import org.springframework.messaging.StubMessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.TestPrincipal;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.MessageBuilder;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link UserDestinationMessageHandler}.
@@ -39,54 +40,60 @@ public class UserDestinationMessageHandlerTests {
private UserDestinationMessageHandler messageHandler;
private MessageSendingOperations<String> messagingTemplate;
@Mock
private SubscribableChannel brokerChannel;
private UserSessionRegistry registry;
@Before
public void setup() {
this.messagingTemplate = Mockito.mock(MessageSendingOperations.class);
MockitoAnnotations.initMocks(this);
this.registry = new DefaultUserSessionRegistry();
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(this.registry);
this.messageHandler = new UserDestinationMessageHandler(this.messagingTemplate, resolver);
this.messageHandler = new UserDestinationMessageHandler(new StubMessageChannel(),
new StubMessageChannel(), this.brokerChannel, resolver);
}
@Test
public void handleSubscribe() {
this.registry.registerSessionId("joe", "123");
when(this.brokerChannel.send(Mockito.any(Message.class))).thenReturn(true);
this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, "joe", "/user/queue/foo"));
ArgumentCaptor<String> captor1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Message> captor2 = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.messagingTemplate).send(captor1.capture(), captor2.capture());
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.brokerChannel).send(captor.capture());
assertEquals("/queue/foo-user123", captor1.getValue());
assertEquals("/queue/foo-user123",
captor.getValue().getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER));
}
@Test
public void handleUnsubscribe() {
this.registry.registerSessionId("joe", "123");
when(this.brokerChannel.send(Mockito.any(Message.class))).thenReturn(true);
this.messageHandler.handleMessage(createMessage(SimpMessageType.UNSUBSCRIBE, "joe", "/user/queue/foo"));
ArgumentCaptor<String> captor1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Message> captor2 = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.messagingTemplate).send(captor1.capture(), captor2.capture());
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.brokerChannel).send(captor.capture());
assertEquals("/queue/foo-user123", captor1.getValue());
assertEquals("/queue/foo-user123",
captor.getValue().getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER));
}
@Test
public void handleMessage() {
this.registry.registerSessionId("joe", "123");
when(this.brokerChannel.send(Mockito.any(Message.class))).thenReturn(true);
this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", "/user/joe/queue/foo"));
ArgumentCaptor<String> captor1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Message> captor2 = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.messagingTemplate).send(captor1.capture(), captor2.capture());
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(this.brokerChannel).send(captor.capture());
assertEquals("/queue/foo-user123", captor1.getValue());
assertEquals("/queue/foo-user123",
captor.getValue().getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER));
}
@@ -95,23 +102,23 @@ public class UserDestinationMessageHandlerTests {
// no destination
this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", null));
Mockito.verifyZeroInteractions(this.messagingTemplate);
Mockito.verifyZeroInteractions(this.brokerChannel);
// not a user destination
this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", "/queue/foo"));
Mockito.verifyZeroInteractions(this.messagingTemplate);
Mockito.verifyZeroInteractions(this.brokerChannel);
// subscribe + no user
this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, null, "/user/queue/foo"));
Mockito.verifyZeroInteractions(this.messagingTemplate);
Mockito.verifyZeroInteractions(this.brokerChannel);
// subscribe + not a user destination
this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, "joe", "/queue/foo"));
Mockito.verifyZeroInteractions(this.messagingTemplate);
Mockito.verifyZeroInteractions(this.brokerChannel);
// no match on message type
this.messageHandler.handleMessage(createMessage(SimpMessageType.CONNECT, "joe", "user/joe/queue/foo"));
Mockito.verifyZeroInteractions(this.messagingTemplate);
Mockito.verifyZeroInteractions(this.brokerChannel);
}

View File

@@ -32,10 +32,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.*;
import org.springframework.messaging.simp.BrokerAvailabilityEvent;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
@@ -92,7 +89,8 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
private void createAndStartRelay() throws InterruptedException {
this.relay = new StompBrokerRelayMessageHandler(this.responseChannel, Arrays.asList("/queue/", "/topic/"));
this.relay = new StompBrokerRelayMessageHandler(new StubMessageChannel(),
this.responseChannel, new StubMessageChannel(), Arrays.asList("/queue/", "/topic/"));
this.relay.setRelayPort(this.port);
this.relay.setApplicationEventPublisher(this.eventPublisher);
this.relay.setSystemHeartbeatReceiveInterval(0);

View File

@@ -53,7 +53,8 @@ public class StompBrokerRelayMessageHandlerTests {
this.tcpClient = new StubTcpOperations();
this.brokerRelay = new StompBrokerRelayMessageHandler(new StubMessageChannel(), Arrays.asList("/topic"));
this.brokerRelay = new StompBrokerRelayMessageHandler(new StubMessageChannel(),
new StubMessageChannel(), new StubMessageChannel(), Arrays.asList("/topic"));
this.brokerRelay.setTcpClient(tcpClient);
}