io.bitsensor.plugins.shaded.org.springframework.messaging.simp.user.UserDestinationMessageHandler Maven / Gradle / Ivy
/*
* Copyright 2002-2016 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 io.bitsensor.plugins.shaded.org.springframework.messaging.simp.user;
import java.util.Arrays;
import java.util.List;
import io.bitsensor.plugins.shaded.org.apache.commons.logging.Log;
import io.bitsensor.plugins.shaded.org.apache.commons.logging.LogFactory;
import io.bitsensor.plugins.shaded.org.springframework.context.SmartLifecycle;
import io.bitsensor.plugins.shaded.org.springframework.messaging.Message;
import io.bitsensor.plugins.shaded.org.springframework.messaging.MessageHandler;
import io.bitsensor.plugins.shaded.org.springframework.messaging.MessageHeaders;
import io.bitsensor.plugins.shaded.org.springframework.messaging.MessagingException;
import io.bitsensor.plugins.shaded.org.springframework.messaging.SubscribableChannel;
import io.bitsensor.plugins.shaded.org.springframework.messaging.core.MessageSendingOperations;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageType;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessagingTemplate;
import io.bitsensor.plugins.shaded.org.springframework.messaging.support.MessageBuilder;
import io.bitsensor.plugins.shaded.org.springframework.messaging.support.MessageHeaderInitializer;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.StringUtils;
/**
* {@code MessageHandler} with support for "user" destinations.
*
* Listens for messages with "user" destinations, translates their destination
* to actual target destinations unique to the active session(s) of a user, and
* then sends the resolved messages to the broker channel to be delivered.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class UserDestinationMessageHandler implements MessageHandler, SmartLifecycle {
private static final Log logger = LogFactory.getLog(UserDestinationMessageHandler.class);
private final SubscribableChannel clientInboundChannel;
private final SubscribableChannel brokerChannel;
private final UserDestinationResolver destinationResolver;
private final MessageSendingOperations messagingTemplate;
private BroadcastHandler broadcastHandler;
private MessageHeaderInitializer headerInitializer;
private final Object lifecycleMonitor = new Object();
private volatile boolean running = false;
/**
* Create an instance with the given client and broker channels subscribing
* to handle messages from each and then sending any resolved messages to the
* broker channel.
* @param clientInboundChannel messages received from clients.
* @param brokerChannel messages sent to the broker.
* @param resolver the resolver for "user" destinations.
*/
public UserDestinationMessageHandler(SubscribableChannel clientInboundChannel,
SubscribableChannel brokerChannel, UserDestinationResolver resolver) {
Assert.notNull(clientInboundChannel, "'clientInChannel' must not be null");
Assert.notNull(brokerChannel, "'brokerChannel' must not be null");
Assert.notNull(resolver, "resolver must not be null");
this.clientInboundChannel = clientInboundChannel;
this.brokerChannel = brokerChannel;
this.messagingTemplate = new SimpMessagingTemplate(brokerChannel);
this.destinationResolver = resolver;
}
/**
* Return the configured {@link UserDestinationResolver}.
*/
public UserDestinationResolver getUserDestinationResolver() {
return this.destinationResolver;
}
/**
* Set a destination to broadcast messages to that remain unresolved because
* the user is not connected. In a multi-application server scenario this
* gives other application servers a chance to try.
* By default this is not set.
* @param destination the target destination.
*/
public void setBroadcastDestination(String destination) {
this.broadcastHandler = (StringUtils.hasText(destination) ?
new BroadcastHandler(this.messagingTemplate, destination) : null);
}
/**
* Return the configured destination for unresolved messages.
*/
public String getBroadcastDestination() {
return (this.broadcastHandler != null ? this.broadcastHandler.getBroadcastDestination() : null);
}
/**
* Return the messaging template used to send resolved messages to the
* broker channel.
*/
public MessageSendingOperations getBrokerMessagingTemplate() {
return this.messagingTemplate;
}
/**
* Configure a custom {@link MessageHeaderInitializer} to initialize the
* headers of resolved target messages.
* By default this is not set.
*/
public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) {
this.headerInitializer = headerInitializer;
}
/**
* Return the configured header initializer.
*/
public MessageHeaderInitializer getHeaderInitializer() {
return this.headerInitializer;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public final void start() {
synchronized (this.lifecycleMonitor) {
this.clientInboundChannel.subscribe(this);
this.brokerChannel.subscribe(this);
this.running = true;
}
}
@Override
public final void stop() {
synchronized (this.lifecycleMonitor) {
this.running = false;
this.clientInboundChannel.unsubscribe(this);
this.brokerChannel.unsubscribe(this);
}
}
@Override
public final void stop(Runnable callback) {
synchronized (this.lifecycleMonitor) {
stop();
callback.run();
}
}
@Override
public final boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return this.running;
}
}
@Override
public void handleMessage(Message message) throws MessagingException {
if (this.broadcastHandler != null) {
message = this.broadcastHandler.preHandle(message);
if (message == null) {
return;
}
}
UserDestinationResult result = this.destinationResolver.resolveDestination(message);
if (result == null) {
return;
}
if (result.getTargetDestinations().isEmpty()) {
if (logger.isTraceEnabled()) {
logger.trace("No active sessions for user destination: " + result.getSourceDestination());
}
if (this.broadcastHandler != null) {
this.broadcastHandler.handleUnresolved(message);
}
return;
}
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
initHeaders(accessor);
accessor.setNativeHeader(SimpMessageHeaderAccessor.ORIGINAL_DESTINATION, result.getSubscribeDestination());
accessor.setLeaveMutable(true);
message = MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders());
if (logger.isTraceEnabled()) {
logger.trace("Translated " + result.getSourceDestination() + " -> " + result.getTargetDestinations());
}
for (String target : result.getTargetDestinations()) {
this.messagingTemplate.send(target, message);
}
}
private void initHeaders(SimpMessageHeaderAccessor headerAccessor) {
if (getHeaderInitializer() != null) {
getHeaderInitializer().initHeaders(headerAccessor);
}
}
@Override
public String toString() {
return "UserDestinationMessageHandler[" + this.destinationResolver + "]";
}
/**
* A handler that broadcasts locally unresolved messages to the broker and
* also handles similar broadcasts received from the broker.
*/
private static class BroadcastHandler {
private static final List NO_COPY_LIST = Arrays.asList("subscription", "message-id");
private final MessageSendingOperations messagingTemplate;
private final String broadcastDestination;
public BroadcastHandler(MessageSendingOperations template, String destination) {
this.messagingTemplate = template;
this.broadcastDestination = destination;
}
public String getBroadcastDestination() {
return this.broadcastDestination;
}
public Message preHandle(Message message) throws MessagingException {
String destination = SimpMessageHeaderAccessor.getDestination(message.getHeaders());
if (!getBroadcastDestination().equals(destination)) {
return message;
}
SimpMessageHeaderAccessor accessor =
SimpMessageHeaderAccessor.getAccessor(message, SimpMessageHeaderAccessor.class);
if (accessor.getSessionId() == null) {
// Our own broadcast
return null;
}
destination = accessor.getFirstNativeHeader(SimpMessageHeaderAccessor.ORIGINAL_DESTINATION);
if (logger.isTraceEnabled()) {
logger.trace("Checking unresolved user destination: " + destination);
}
SimpMessageHeaderAccessor newAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
for (String name : accessor.toNativeHeaderMap().keySet()) {
if (NO_COPY_LIST.contains(name)) {
continue;
}
newAccessor.setNativeHeader(name, accessor.getFirstNativeHeader(name));
}
newAccessor.setDestination(destination);
newAccessor.setHeader(SimpMessageHeaderAccessor.IGNORE_ERROR, true); // ensure send doesn't block
return MessageBuilder.createMessage(message.getPayload(), newAccessor.getMessageHeaders());
}
public void handleUnresolved(Message message) {
MessageHeaders headers = message.getHeaders();
if (SimpMessageHeaderAccessor.getFirstNativeHeader(
SimpMessageHeaderAccessor.ORIGINAL_DESTINATION, headers) != null) {
// Re-broadcast
return;
}
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
String destination = accessor.getDestination();
accessor.setNativeHeader(SimpMessageHeaderAccessor.ORIGINAL_DESTINATION, destination);
accessor.setLeaveMutable(true);
message = MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders());
if (logger.isTraceEnabled()) {
logger.trace("Translated " + destination + " -> " + getBroadcastDestination());
}
this.messagingTemplate.send(getBroadcastDestination(), message);
}
}
}