diff --git a/spring-context/src/main/java/org/springframework/messaging/MessageHandler.java b/spring-context/src/main/java/org/springframework/messaging/MessageHandler.java
new file mode 100644
index 0000000000..1cbda2cf31
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/messaging/MessageHandler.java
@@ -0,0 +1,51 @@
+/*
+ * 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.messaging;
+
+
+/**
+ * Base interface for any component that handles Messages.
+ *
+ * @author Mark Fisher
+ * @author Iwein Fuld
+ * @since 4.0
+ */
+public interface MessageHandler {
+
+ /**
+ * TODO: support exceptions?
+ *
+ * Handles the message if possible. If the handler cannot deal with the
+ * message this will result in a MessageRejectedException e.g.
+ * in case of a Selective Consumer. When a consumer tries to handle a
+ * message, but fails to do so, a MessageHandlingException is
+ * thrown. In the last case it is recommended to treat the message as tainted
+ * and go into an error scenario.
+ *
+ * When the handling results in a failure of another message being sent
+ * (e.g. a "reply" message), that failure will trigger a
+ * MessageDeliveryException.
+ *
+ * @param message the message to be handled
+ * @throws org.springframework.integration.MessageRejectedException if the handler doesn't accept the message
+ * @throws org.springframework.integration.MessageHandlingException when something fails during the handling
+ * @throws org.springframework.integration.MessageDeliveryException when this handler failed to deliver the
+ * reply related to the handling of the message
+ */
+ void handleMessage(Message> message) throws MessagingException;
+
+}
diff --git a/spring-context/src/main/java/org/springframework/messaging/SubscribableChannel.java b/spring-context/src/main/java/org/springframework/messaging/SubscribableChannel.java
new file mode 100644
index 0000000000..e7ff7385c7
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/messaging/SubscribableChannel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.messaging;
+
+
+/**
+ * Interface for any MessageChannel implementation that accepts subscribers.
+ * The subscribers must implement the {@link MessageHandler} interface and
+ * will be invoked when a Message is available.
+ *
+ * @author Mark Fisher
+ * @since 4.0
+ */
+public interface SubscribableChannel extends MessageChannel {
+
+ /**
+ * Register a {@link MessageHandler} as a subscriber to this channel.
+ */
+ boolean subscribe(MessageHandler handler);
+
+ /**
+ * Remove a {@link MessageHandler} from the subscribers of this channel.
+ */
+ boolean unsubscribe(MessageHandler handler);
+
+}
diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractMessageService.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java
similarity index 50%
rename from spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractMessageService.java
rename to spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java
index 89816b75b1..335655ef76 100644
--- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractMessageService.java
+++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java
@@ -18,37 +18,35 @@ package org.springframework.web.messaging.service;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.MessagingException;
+import org.springframework.messaging.SubscribableChannel;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.messaging.MessageType;
import org.springframework.web.messaging.PubSubHeaders;
-import org.springframework.web.messaging.event.EventBus;
-import org.springframework.web.messaging.event.EventConsumer;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
-public abstract class AbstractMessageService {
+public abstract class AbstractPubSubMessageHandler implements MessageHandler {
protected final Log logger = LogFactory.getLog(getClass());
+ private final MessageChannel publishChannel;
- public static final String CLIENT_TO_SERVER_MESSAGE_KEY = "clientToServerMessageKey";
-
- public static final String CLIENT_CONNECTION_CLOSED_KEY = "clientConnectionClosed";
-
- public static final String SERVER_TO_CLIENT_MESSAGE_KEY = "serverToClientMessageKey";
-
-
- private final EventBus eventBus;
+ private final MessageChannel clientChannel;
private final List allowedDestinations = new ArrayList();
@@ -57,56 +55,31 @@ public abstract class AbstractMessageService {
private final PathMatcher pathMatcher = new AntPathMatcher();
- public AbstractMessageService(EventBus reactor) {
+ /**
+ * @param publishChannel a channel for publishing messages from within the
+ * application; this constructor will also automatically subscribe the
+ * current instance to this channel
+ *
+ * @param clientChannel a channel for sending messages to connected clients.
+ */
+ public AbstractPubSubMessageHandler(SubscribableChannel publishChannel, MessageChannel clientChannel) {
- Assert.notNull(reactor, "reactor is required");
- this.eventBus = reactor;
+ Assert.notNull(publishChannel, "publishChannel is required");
+ Assert.notNull(clientChannel, "clientChannel is required");
- this.eventBus.registerConsumer(CLIENT_TO_SERVER_MESSAGE_KEY, new EventConsumer>() {
+ publishChannel.subscribe(this);
+ this.publishChannel = publishChannel;
- @Override
- public void accept(Message> message) {
-
- if (!isAllowedDestination(message)) {
- return;
- }
-
- if (logger.isTraceEnabled()) {
- logger.trace("Processing message id=" + message.getHeaders().getId());
- }
-
- PubSubHeaders headers = PubSubHeaders.fromMessageHeaders(message.getHeaders());
- MessageType messageType = headers.getMessageType();
- if (messageType == null || messageType.equals(MessageType.OTHER)) {
- processOther(message);
- }
- else if (MessageType.CONNECT.equals(messageType)) {
- processConnect(message);
- }
- else if (MessageType.MESSAGE.equals(messageType)) {
- processMessage(message);
- }
- else if (MessageType.SUBSCRIBE.equals(messageType)) {
- processSubscribe(message);
- }
- else if (MessageType.UNSUBSCRIBE.equals(messageType)) {
- processUnsubscribe(message);
- }
- else if (MessageType.DISCONNECT.equals(messageType)) {
- processDisconnect(message);
- }
- }
- });
-
- this.eventBus.registerConsumer(CLIENT_CONNECTION_CLOSED_KEY, new EventConsumer() {
-
- @Override
- public void accept(String sessionId) {
- processClientConnectionClosed(sessionId);
- }
- });
+ this.clientChannel = clientChannel;
}
+ public MessageChannel getPublishChannel() {
+ return this.publishChannel;
+ }
+
+ public MessageChannel getClientChannel() {
+ return this.clientChannel;
+ }
/**
* Ant-style destination patterns that this service is allowed to process.
@@ -124,16 +97,29 @@ public abstract class AbstractMessageService {
this.disallowedDestinations.addAll(Arrays.asList(patterns));
}
- public EventBus getEventBus() {
- return this.eventBus;
+ protected abstract Collection getSupportedMessageTypes();
+
+
+ protected boolean canHandle(Message> message, MessageType messageType) {
+
+ if (!CollectionUtils.isEmpty(getSupportedMessageTypes())) {
+ if (!getSupportedMessageTypes().contains(messageType)) {
+ return false;
+ }
+ }
+
+ return isDestinationAllowed(message);
}
- private boolean isAllowedDestination(Message> message) {
+ protected boolean isDestinationAllowed(Message> message) {
+
PubSubHeaders headers = PubSubHeaders.fromMessageHeaders(message.getHeaders());
String destination = headers.getDestination();
+
if (destination == null) {
return true;
}
+
if (!this.disallowedDestinations.isEmpty()) {
for (String pattern : this.disallowedDestinations) {
if (this.pathMatcher.match(pattern, destination)) {
@@ -144,6 +130,7 @@ public abstract class AbstractMessageService {
}
}
}
+
if (!this.allowedDestinations.isEmpty()) {
for (String pattern : this.allowedDestinations) {
if (this.pathMatcher.match(pattern, destination)) {
@@ -155,28 +142,61 @@ public abstract class AbstractMessageService {
}
return false;
}
+
return true;
}
- protected void processConnect(Message> message) {
+
+ @Override
+ public final void handleMessage(Message> message) throws MessagingException {
+
+ PubSubHeaders headers = PubSubHeaders.fromMessageHeaders(message.getHeaders());
+ MessageType messageType = headers.getMessageType();
+
+ if (!canHandle(message, messageType)) {
+ return;
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Handling message id=" + message.getHeaders().getId());
+ }
+
+ if (MessageType.MESSAGE.equals(messageType)) {
+ handlePublish(message);
+ }
+ else if (MessageType.SUBSCRIBE.equals(messageType)) {
+ handleSubscribe(message);
+ }
+ else if (MessageType.UNSUBSCRIBE.equals(messageType)) {
+ handleUnsubscribe(message);
+ }
+ else if (MessageType.CONNECT.equals(messageType)) {
+ handleConnect(message);
+ }
+ else if (MessageType.DISCONNECT.equals(messageType)) {
+ handleDisconnect(message);
+ }
+ else {
+ handleOther(message);
+ }
}
- protected void processMessage(Message> message) {
+ protected void handleConnect(Message> message) {
}
- protected void processSubscribe(Message> message) {
+ protected void handlePublish(Message> message) {
}
- protected void processUnsubscribe(Message> message) {
+ protected void handleSubscribe(Message> message) {
}
- protected void processDisconnect(Message> message) {
+ protected void handleUnsubscribe(Message> message) {
}
- protected void processOther(Message> message) {
+ protected void handleDisconnect(Message> message) {
}
- protected void processClientConnectionClosed(String sessionId) {
+ protected void handleOther(Message> message) {
}
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/PubSubMessageService.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java
similarity index 57%
rename from spring-websocket/src/main/java/org/springframework/web/messaging/service/PubSubMessageService.java
rename to spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java
index b81cdc03a7..3d0813d744 100644
--- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/PubSubMessageService.java
+++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java
@@ -17,34 +17,47 @@
package org.springframework.web.messaging.service;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.messaging.GenericMessage;
import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.web.messaging.MessageType;
import org.springframework.web.messaging.PubSubHeaders;
import org.springframework.web.messaging.converter.CompositeMessageConverter;
import org.springframework.web.messaging.converter.MessageConverter;
-import org.springframework.web.messaging.event.EventBus;
-import org.springframework.web.messaging.event.EventConsumer;
-import org.springframework.web.messaging.event.EventRegistration;
+
+import reactor.core.Reactor;
+import reactor.fn.Consumer;
+import reactor.fn.Event;
+import reactor.fn.registry.Registration;
+import reactor.fn.selector.ObjectSelector;
+import reactor.fn.selector.Selector;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
-public class PubSubMessageService extends AbstractMessageService {
+public class ReactorPubSubMessageHandler extends AbstractPubSubMessageHandler {
+
+ private final Reactor reactor;
private MessageConverter payloadConverter;
- private Map> subscriptionsBySession =
- new ConcurrentHashMap>();
+ private Map>> subscriptionsBySession = new ConcurrentHashMap>>();
- public PubSubMessageService(EventBus reactor) {
- super(reactor);
+ public ReactorPubSubMessageHandler(SubscribableChannel publishChannel, MessageChannel clientChannel,
+ Reactor reactor) {
+
+ super(publishChannel, clientChannel);
+ this.reactor = reactor;
this.payloadConverter = new CompositeMessageConverter(null);
}
@@ -54,7 +67,7 @@ public class PubSubMessageService extends AbstractMessageService {
}
@Override
- protected void processMessage(Message> message) {
+ public void handlePublish(Message> message) {
if (logger.isDebugEnabled()) {
logger.debug("Message received: " + message);
@@ -66,7 +79,7 @@ public class PubSubMessageService extends AbstractMessageService {
byte[] payload = payloadConverter.convertToPayload(message.getPayload(), inHeaders.getContentType());
message = new GenericMessage(payload, message.getHeaders());
- getEventBus().send(getPublishKey(inHeaders.getDestination()), message);
+ this.reactor.notify(getPublishKey(inHeaders.getDestination()), Event.wrap(message));
}
catch (Exception ex) {
logger.error("Failed to publish " + message, ex);
@@ -78,56 +91,69 @@ public class PubSubMessageService extends AbstractMessageService {
}
@Override
- protected void processSubscribe(Message> message) {
+ protected Collection getSupportedMessageTypes() {
+ return Arrays.asList(MessageType.MESSAGE, MessageType.SUBSCRIBE, MessageType.UNSUBSCRIBE);
+ }
+
+
+ @Override
+ public void handleSubscribe(Message> message) {
+
if (logger.isDebugEnabled()) {
logger.debug("Subscribe " + message);
}
+
PubSubHeaders headers = PubSubHeaders.fromMessageHeaders(message.getHeaders());
final String subscriptionId = headers.getSubscriptionId();
- EventRegistration registration = getEventBus().registerConsumer(getPublishKey(headers.getDestination()),
- new EventConsumer>() {
+
+ Selector selector = new ObjectSelector(getPublishKey(headers.getDestination()));
+ Registration> registration = this.reactor.on(selector,
+ new Consumer>>() {
@Override
- public void accept(Message> message) {
+ public void accept(Event> event) {
+ Message> message = event.getData();
PubSubHeaders inHeaders = PubSubHeaders.fromMessageHeaders(message.getHeaders());
PubSubHeaders outHeaders = PubSubHeaders.create();
outHeaders.setDestinations(inHeaders.getDestinations());
- outHeaders.setContentType(inHeaders.getContentType());
+ if (inHeaders.getContentType() != null) {
+ outHeaders.setContentType(inHeaders.getContentType());
+ }
outHeaders.setSubscriptionId(subscriptionId);
Object payload = message.getPayload();
message = new GenericMessage