/*
* 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 org.springframework.integration.dispatcher;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
import org.springframework.integration.MessageDispatchingException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.MessageHandlingRunnable;
import org.springframework.util.Assert;
/**
* Implementation of {@link MessageDispatcher} that will attempt to send a
* {@link Message} to at most one of its handlers. The handlers will be tried
* as determined by the {@link LoadBalancingStrategy} if one is configured. As
* soon as <em>one</em> of the handlers accepts the Message, the dispatcher will
* return <code>true</code> and ignore the rest of its handlers.
* <p>
* If the dispatcher has no handlers, a {@link MessageDeliveryException} will be
* thrown. If all handlers throw Exceptions, the dispatcher will throw an
* {@link AggregateMessageDeliveryException}.
* <p>
* A load-balancing strategy may be provided to this class to control the order in
* which the handlers will be tried.
*
* @author Iwein Fuld
* @author Mark Fisher
* @author Gary Russell
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 1.0.2
*/
public class UnicastingDispatcher extends AbstractDispatcher {
private final MessageHandler dispatchHandler = message -> doDispatch(message);
private final Executor executor;
private volatile boolean failover = true;
private volatile LoadBalancingStrategy loadBalancingStrategy;
private volatile MessageHandlingTaskDecorator messageHandlingTaskDecorator = task -> task;
public UnicastingDispatcher() {
this.executor = null;
}
public UnicastingDispatcher(Executor executor) {
this.executor = executor;
}
/**
* Specify whether this dispatcher should failover when a single
* {@link MessageHandler} throws an Exception. The default value is
* <code>true</code>.
*
* @param failover The failover boolean.
*/
public void setFailover(boolean failover) {
this.failover = failover;
}
/**
* Provide a {@link LoadBalancingStrategy} for this dispatcher.
*
* @param loadBalancingStrategy The load balancing strategy implementation.
*/
public void setLoadBalancingStrategy(LoadBalancingStrategy loadBalancingStrategy) {
this.loadBalancingStrategy = loadBalancingStrategy;
}
public void setMessageHandlingTaskDecorator(MessageHandlingTaskDecorator messageHandlingTaskDecorator) {
Assert.notNull(messageHandlingTaskDecorator, "'messageHandlingTaskDecorator' must not be null.");
this.messageHandlingTaskDecorator = messageHandlingTaskDecorator;
}
@Override
public final boolean dispatch(final Message<?> message) {
if (this.executor != null) {
Runnable task = createMessageHandlingTask(message);
this.executor.execute(task);
return true;
}
return this.doDispatch(message);
}
private Runnable createMessageHandlingTask(final Message<?> message) {
MessageHandlingRunnable task = new MessageHandlingRunnable() {
@Override
public void run() {
doDispatch(message);
}
@Override
public Message<?> getMessage() {
return message;
}
@Override
public MessageHandler getMessageHandler() {
return UnicastingDispatcher.this.dispatchHandler;
}
};
return this.messageHandlingTaskDecorator.decorate(task);
}
private boolean doDispatch(Message<?> message) {
if (tryOptimizedDispatch(message)) {
return true;
}
boolean success = false;
Iterator<MessageHandler> handlerIterator = this.getHandlerIterator(message);
if (!handlerIterator.hasNext()) {
throw new MessageDispatchingException(message, "Dispatcher has no subscribers");
}
List<RuntimeException> exceptions = new ArrayList<RuntimeException>();
while (!success && handlerIterator.hasNext()) {
MessageHandler handler = handlerIterator.next();
try {
handler.handleMessage(message);
success = true; // we have a winner.
}
catch (Exception e) {
RuntimeException runtimeException = this.wrapExceptionIfNecessary(message, e);
exceptions.add(runtimeException);
this.handleExceptions(exceptions, message, !handlerIterator.hasNext());
}
}
return success;
}
/**
* Returns the iterator that will be used to loop over the handlers.
* Delegates to a {@link LoadBalancingStrategy} if available. Otherwise,
* it simply returns the Iterator for the existing handler List.
*/
private Iterator<MessageHandler> getHandlerIterator(Message<?> message) {
if (this.loadBalancingStrategy != null) {
return this.loadBalancingStrategy.getHandlerIterator(message, this.getHandlers());
}
return this.getHandlers().iterator();
}
/**
* Handles Exceptions that occur while dispatching. If this dispatcher has
* failover enabled, it will only throw an Exception when the handler list
* is exhausted. The 'isLast' flag will be <em>true</em> if the
* Exception occurred during the final iteration of the MessageHandlers.
* If failover is disabled for this dispatcher, it will re-throw any
* Exception immediately.
*/
private void handleExceptions(List<RuntimeException> allExceptions, Message<?> message, boolean isLast) {
if (isLast || !this.failover) {
if (allExceptions != null && allExceptions.size() == 1) {
throw allExceptions.get(0);
}
throw new AggregateMessageDeliveryException(message, //NOSONAR - false positive
"All attempts to deliver Message to MessageHandlers failed.", allExceptions);
}
}
}