/*
* 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.Collection;
import java.util.UUID;
import java.util.concurrent.Executor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.MessageDispatchingException;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.integration.support.MessageDecorator;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.MessageHandlingRunnable;
import org.springframework.util.Assert;
/**
* A broadcasting dispatcher implementation. If the 'ignoreFailures' property is set to <code>false</code> (the
* default), it will fail fast such that any Exception thrown by a MessageHandler may prevent subsequent handlers from
* receiving the Message. However, when an Executor is provided, the Messages may be dispatched in separate Threads so
* that other handlers are invoked even when the 'ignoreFailures' flag is <code>false</code>.
* <p>
* If the 'ignoreFailures' flag is set to <code>true</code> on the other hand, it will make a best effort to send the
* message to each of its handlers. In other words, when 'ignoreFailures' is <code>true</code>, if it fails to send to
* any one handler, it will simply log a warn-level message but continue to send the Message to any other handlers.
*
* @author Mark Fisher
* @author Iwein Fuld
* @author Gary Russell
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class BroadcastingDispatcher extends AbstractDispatcher implements BeanFactoryAware {
private final boolean requireSubscribers;
private volatile boolean ignoreFailures;
private volatile boolean applySequence;
private final Executor executor;
private volatile int minSubscribers;
private volatile MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
private volatile boolean messageBuilderFactorySet;
private volatile MessageHandlingTaskDecorator messageHandlingTaskDecorator = task -> task;
private BeanFactory beanFactory;
public BroadcastingDispatcher() {
this(null, false);
}
public BroadcastingDispatcher(Executor executor) {
this(executor, false);
}
public BroadcastingDispatcher(boolean requireSubscribers) {
this(null, requireSubscribers);
}
public BroadcastingDispatcher(Executor executor, boolean requireSubscribers) {
this.requireSubscribers = requireSubscribers;
this.executor = executor;
}
/**
* Specify whether failures for one or more of the handlers should be ignored. By default this is <code>false</code>
* meaning that an Exception will be thrown when a handler fails. To override this and suppress Exceptions, set the
* value to <code>true</code>.
* <p>
* Keep in mind that when using an Executor, even without ignoring the failures, other handlers may be invoked after
* one throws an Exception. Since the Executor is most likely using a different thread, this flag would only affect
* whether an error Message is sent to the error channel or not in the case that such an Executor has been
* configured.
*
* @param ignoreFailures true when failures are to be ignored.
*/
public void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures;
}
/**
* Specify whether to apply sequence numbers to the messages prior to sending to the handlers. By default, sequence
* numbers will <em>not</em> be applied.
*
* @param applySequence true when sequence information should be applied.
*/
public void setApplySequence(boolean applySequence) {
this.applySequence = applySequence;
}
/**
* If at least this number of subscribers receive the message, {@link #dispatch(Message)}
* will return true. Default: 0.
* @param minSubscribers The minimum number of subscribers.
*/
public void setMinSubscribers(int minSubscribers) {
this.minSubscribers = minSubscribers;
}
public void setMessageHandlingTaskDecorator(MessageHandlingTaskDecorator messageHandlingTaskDecorator) {
Assert.notNull(messageHandlingTaskDecorator, "'messageHandlingTaskDecorator' must not be null.");
this.messageHandlingTaskDecorator = messageHandlingTaskDecorator;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
protected MessageBuilderFactory getMessageBuilderFactory() {
if (!this.messageBuilderFactorySet) {
if (this.beanFactory != null) {
this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory);
}
this.messageBuilderFactorySet = true;
}
return this.messageBuilderFactory;
}
@Override
public boolean dispatch(Message<?> message) {
int dispatched = 0;
int sequenceNumber = 1;
Collection<MessageHandler> handlers = this.getHandlers();
if (this.requireSubscribers && handlers.size() == 0) {
throw new MessageDispatchingException(message, "Dispatcher has no subscribers");
}
int sequenceSize = handlers.size();
Message<?> messageToSend = message;
UUID sequenceId = null;
if (this.applySequence) {
sequenceId = message.getHeaders().getId();
}
for (MessageHandler handler : handlers) {
if (this.applySequence) {
messageToSend = getMessageBuilderFactory()
.fromMessage(message)
.pushSequenceDetails(sequenceId, sequenceNumber++, sequenceSize)
.build();
if (message instanceof MessageDecorator) {
messageToSend = ((MessageDecorator) message).decorateMessage(messageToSend);
}
}
if (this.executor != null) {
Runnable task = createMessageHandlingTask(handler, messageToSend);
this.executor.execute(task);
dispatched++;
}
else {
if (this.invokeHandler(handler, messageToSend)) {
dispatched++;
}
}
}
if (dispatched == 0 && this.minSubscribers == 0 && logger.isDebugEnabled()) {
if (sequenceSize > 0) {
logger.debug("No subscribers received message, default behavior is ignore");
}
else {
logger.debug("No subscribers, default behavior is ignore");
}
}
return dispatched >= this.minSubscribers;
}
private Runnable createMessageHandlingTask(final MessageHandler handler, final Message<?> message) {
MessageHandlingRunnable task = new MessageHandlingRunnable() {
private final MessageHandler delegate = message1 -> invokeHandler(handler, message1);
@Override
public void run() {
invokeHandler(handler, message);
}
@Override
public Message<?> getMessage() {
return message;
}
@Override
public MessageHandler getMessageHandler() {
return this.delegate;
}
};
return this.messageHandlingTaskDecorator.decorate(task);
}
private boolean invokeHandler(MessageHandler handler, Message<?> message) {
try {
handler.handleMessage(message);
return true;
}
catch (RuntimeException e) {
if (!this.ignoreFailures) {
if (e instanceof MessagingException && ((MessagingException) e).getFailedMessage() == null) {
throw new MessagingException(message, "Failed to handle Message", e);
}
throw e;
}
else if (this.logger.isWarnEnabled()) {
logger.warn("Suppressing Exception since 'ignoreFailures' is set to TRUE.", e);
}
return false;
}
}
}