From 1c91a52639ee61aa901b608c9115087de617536d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sat, 28 Jun 2014 11:07:42 -0400 Subject: [PATCH] Add STOMP subscribe/unscubscribe ApplicationContext events Issue: SPR-11813 --- .../messaging/AbstractSubProtocolEvent.java | 71 +++++++++++++++++++ .../socket/messaging/SessionConnectEvent.java | 41 +---------- .../messaging/SessionConnectedEvent.java | 31 ++------ .../messaging/SessionDisconnectEvent.java | 2 +- .../messaging/SessionSubscribeEvent.java | 37 ++++++++++ .../messaging/SessionUnsubscribeEvent.java | 37 ++++++++++ .../messaging/StompSubProtocolHandler.java | 12 +++- .../StompSubProtocolHandlerTests.java | 16 ++++- src/asciidoc/index.adoc | 2 + 9 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/messaging/AbstractSubProtocolEvent.java create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionSubscribeEvent.java create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionUnsubscribeEvent.java diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/AbstractSubProtocolEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/AbstractSubProtocolEvent.java new file mode 100644 index 0000000000..4825485b1c --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/AbstractSubProtocolEvent.java @@ -0,0 +1,71 @@ +/* + * 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. + * 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.socket.messaging; + + +import org.springframework.context.ApplicationEvent; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +/** + * A base class for events for a message received from a WebSocket client and + * parsed into a higher level sub-protocol (e.g. STOMP). + * + * @author Rossen Stoyanchev + * @since 4.0.3 + */ +@SuppressWarnings("serial") +public abstract class AbstractSubProtocolEvent extends ApplicationEvent { + + private final Message message; + + + /** + * Create a new SessionConnectEvent. + * + * @param source the component that published the event (never {@code null}) + * @param message the connect message + */ + protected AbstractSubProtocolEvent(Object source, Message message) { + super(source); + Assert.notNull(message, "'message' must not be null"); + this.message = message; + } + + /** + * Return the Message associated with the event. Here is an example of + * obtaining information about the session id or any headers in the + * message: + * + *
+	 * StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
+	 * headers.getSessionId();
+	 * headers.getSessionAttributes();
+	 * headers.getPrincipal();
+	 * 
+ * + */ + public Message getMessage() { + return this.message; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + this.message + "]"; + } +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java index 52c4f0e34e..aeb229ae4a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java @@ -17,9 +17,7 @@ package org.springframework.web.socket.messaging; -import org.springframework.context.ApplicationEvent; import org.springframework.messaging.Message; -import org.springframework.util.Assert; /** * Event raised when a new WebSocket client using a Simple Messaging Protocol @@ -29,50 +27,15 @@ import org.springframework.util.Assert; * but rather the client's first attempt to connect within the the sub-protocol, * for example sending the STOMP CONNECT frame. * - *

The provided {@link #getMessage() message} can be examined to check - * information about the connected user, The session id, and any headers - * sent by the client, for STOMP check the class - * {@link org.springframework.messaging.simp.stomp.StompHeaderAccessor}. - * For example: - * - *

- * StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
- * headers.getSessionId();
- * headers.getSessionAttributes();
- * headers.getPrincipal();
- * 
- * * @author Rossen Stoyanchev * @since 4.0.3 */ @SuppressWarnings("serial") -public class SessionConnectEvent extends ApplicationEvent { +public class SessionConnectEvent extends AbstractSubProtocolEvent { - private final Message message; - - /** - * Create a new SessionConnectEvent. - * - * @param source the component that published the event (never {@code null}) - * @param message the connect message - */ public SessionConnectEvent(Object source, Message message) { - super(source); - Assert.notNull(message, "'message' must not be null"); - this.message = message; + super(source, message); } - /** - * Return the connect message. - */ - public Message getMessage() { - return this.message; - } - - - @Override - public String toString() { - return "SessionConnectEvent" + this.message; - } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectedEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectedEvent.java index 01498d565b..b6b467b85d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectedEvent.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectedEvent.java @@ -17,45 +17,22 @@ package org.springframework.web.socket.messaging; -import org.springframework.context.ApplicationEvent; import org.springframework.messaging.Message; -import org.springframework.util.Assert; /** * A connected event represents the server response to a client's connect request. - * See {@link org.springframework.web.socket.messaging.SessionConnectEvent}. + * See {@link org.springframework.web.socket.messaging.SessionConnectEvent + * SessionConnectEvent}. * * @author Rossen Stoyanchev * @since 4.0.3 */ @SuppressWarnings("serial") -public class SessionConnectedEvent extends ApplicationEvent { +public class SessionConnectedEvent extends AbstractSubProtocolEvent { - private final Message message; - - /** - * Create a new event. - * - * @param source the component that published the event (never {@code null}) - * @param message the connected message - */ public SessionConnectedEvent(Object source, Message message) { - super(source); - Assert.notNull(message, "'message' must not be null"); - this.message = message; - } - - /** - * Return the connected message. - */ - public Message getMessage() { - return this.message; + super(source, message); } - - @Override - public String toString() { - return "SessionConnectedEvent" + this.message; - } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java index 15ad6303a1..02a76a37ba 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java @@ -68,7 +68,7 @@ public class SessionDisconnectEvent extends ApplicationEvent { @Override public String toString() { - return "SessionDisconnectEvent[sessionId=" + this.sessionId + + return "SessionDisconnectEvent[sessionId=" + this.sessionId + ", " + (this.status != null ? this.status.toString() : "closeStatus=null") + "]"; } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionSubscribeEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionSubscribeEvent.java new file mode 100644 index 0000000000..9e2129bd6e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionSubscribeEvent.java @@ -0,0 +1,37 @@ +/* + * 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. + * 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.socket.messaging; + + +import org.springframework.messaging.Message; + +/** + * Event raised when a new WebSocket client using a Simple Messaging Protocol + * (e.g. STOMP) sends a subscription request. + * + * @author Rossen Stoyanchev + * @since 4.0.3 + */ +@SuppressWarnings("serial") +public class SessionSubscribeEvent extends AbstractSubProtocolEvent { + + + public SessionSubscribeEvent(Object source, Message message) { + super(source, message); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionUnsubscribeEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionUnsubscribeEvent.java new file mode 100644 index 0000000000..a0cdaca447 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionUnsubscribeEvent.java @@ -0,0 +1,37 @@ +/* + * 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. + * 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.socket.messaging; + + +import org.springframework.messaging.Message; + +/** + * Event raised when a new WebSocket client using a Simple Messaging Protocol + * (e.g. STOMP) sends a request to remove a subscription. + * + * @author Rossen Stoyanchev + * @since 4.0.3 + */ +@SuppressWarnings("serial") +public class SessionUnsubscribeEvent extends AbstractSubProtocolEvent { + + + public SessionUnsubscribeEvent(Object source, Message message) { + super(source, message); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java index fd1149a110..9c42025ab4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java @@ -219,8 +219,16 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE headerAccessor.setUser(session.getPrincipal()); headerAccessor.setImmutable(); - if (this.eventPublisher != null && StompCommand.CONNECT.equals(headerAccessor.getCommand())) { - publishEvent(new SessionConnectEvent(this, message)); + if (this.eventPublisher != null) { + if (StompCommand.CONNECT.equals(headerAccessor.getCommand())) { + publishEvent(new SessionConnectEvent(this, message)); + } + else if (StompCommand.SUBSCRIBE.equals(headerAccessor.getCommand())) { + publishEvent(new SessionSubscribeEvent(this, message)); + } + else if (StompCommand.UNSUBSCRIBE.equals(headerAccessor.getCommand())) { + publishEvent(new SessionUnsubscribeEvent(this, message)); + } } try { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java index 506f6bb225..99e01a6fab 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java @@ -184,12 +184,24 @@ public class StompSubProtocolHandlerTests { message = MessageBuilder.createMessage(EMPTY_PAYLOAD, headers.getMessageHeaders()); this.protocolHandler.handleMessageToClient(this.session, message); + headers = StompHeaderAccessor.create(StompCommand.SUBSCRIBE); + message = MessageBuilder.createMessage(EMPTY_PAYLOAD, headers.getMessageHeaders()); + textMessage = new TextMessage(new StompEncoder().encode(message)); + this.protocolHandler.handleMessageFromClient(this.session, textMessage, this.channel); + + headers = StompHeaderAccessor.create(StompCommand.UNSUBSCRIBE); + message = MessageBuilder.createMessage(EMPTY_PAYLOAD, headers.getMessageHeaders()); + textMessage = new TextMessage(new StompEncoder().encode(message)); + this.protocolHandler.handleMessageFromClient(this.session, textMessage, this.channel); + this.protocolHandler.afterSessionEnded(this.session, CloseStatus.BAD_DATA, this.channel); - assertEquals("Unexpected events " + publisher.events, 3, publisher.events.size()); + assertEquals("Unexpected events " + publisher.events, 5, publisher.events.size()); assertEquals(SessionConnectEvent.class, publisher.events.get(0).getClass()); assertEquals(SessionConnectedEvent.class, publisher.events.get(1).getClass()); - assertEquals(SessionDisconnectEvent.class, publisher.events.get(2).getClass()); + assertEquals(SessionSubscribeEvent.class, publisher.events.get(2).getClass()); + assertEquals(SessionUnsubscribeEvent.class, publisher.events.get(3).getClass()); + assertEquals(SessionDisconnectEvent.class, publisher.events.get(4).getClass()); } @Test diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 501f0b2557..6ec2af052d 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -38417,6 +38417,8 @@ to this event can wrap the contained message using `SimpMessageHeaderAccessor` o * `SessionConnectedEvent` -- published shortly after a `SessionConnectEvent` when the broker has sent a STOMP CONNECTED frame in response to the CONNECT. At this point the STOMP session can be considered fully established. +* `SessionSubscribeEvent` -- published when a new STOMP SUBSCRIBE is received. +* `SessionUnsubscribeEvent` -- published when a new STOMP UNSUBSCRIBE is received. * `SessionDisconnectEvent` -- published when a STOMP session ends. The DISCONNECT may have been sent from the client or it may also be automatically generated when the WebSocket session is closed. In some cases this event may be published more than once