org.mule.routing.ScatterGatherRouter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mule-core Show documentation
Show all versions of mule-core Show documentation
Mule server and core classes
/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.routing;
import org.mule.DefaultMuleEvent;
import org.mule.OptimizedRequestContext;
import org.mule.api.DefaultMuleException;
import org.mule.api.ExceptionPayload;
import org.mule.api.MessagingException;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.MuleMessageCollection;
import org.mule.api.config.ThreadingProfile;
import org.mule.api.context.WorkManager;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.processor.MessageProcessor;
import org.mule.api.processor.MessageProcessorChain;
import org.mule.api.processor.MessageRouter;
import org.mule.api.routing.AggregationContext;
import org.mule.api.routing.CouldNotRouteOutboundMessageException;
import org.mule.api.routing.ResponseTimeoutException;
import org.mule.api.routing.RoutePathNotFoundException;
import org.mule.api.transport.DispatchException;
import org.mule.config.i18n.CoreMessages;
import org.mule.config.i18n.MessageFactory;
import org.mule.message.DefaultExceptionPayload;
import org.mule.processor.AbstractMessageProcessorOwner;
import org.mule.processor.chain.DefaultMessageProcessorChainBuilder;
import org.mule.routing.outbound.MulticastingRouter;
import org.mule.util.Preconditions;
import org.mule.util.concurrent.ThreadNameHelper;
import org.mule.work.ProcessingMuleEventWork;
import org.mule.work.SerialWorkManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.resource.spi.work.WorkException;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* The Scatter-Gather
router will broadcast copies of the current
* message to every endpoint registered with the router in parallel.
*
* It is very similar to the <all>
implemented in the
* {@link MulticastingRouter} class, except that this router processes in parallel
* instead of sequentially.
*
* Differences with {@link MulticastingRouter} router:
*
*
* - When using {@link MulticastingRouter} changes to the payload performed in
* route n are visible in route (n+1). When using {@link ScatterGatherRouter}, each
* route has different shallow copies of the original event
* - {@link MulticastingRouter} throws
* {@link CouldNotRouteOutboundMessageException} upon route failure and stops
* processing. When catching the exception, you'll have no information about the
* result of any prior routes. {@link ScatterGatherRouter} will process all routes no
* matter what. It will also aggregate the results of all routes into a
* {@link MuleMessageCollection} in which each entry has the {@link ExceptionPayload}
* set accordingly and then will throw a {@link CompositeRoutingException} which will
* give you visibility over the output of other routes.
*
*
* For advanced use cases, a custom {@link AggregationStrategy} can be applied to
* customize the logic used to aggregate the route responses back into one single
* element or to throw exception
*
*
* @since 3.5.0
*/
public class ScatterGatherRouter extends AbstractMessageProcessorOwner implements MessageRouter
{
private static final Logger logger = LoggerFactory.getLogger(ScatterGatherRouter.class);
/**
* Timeout in milliseconds to be applied to each route. Values lower or equal to
* zero means no timeout
*/
private long timeout = 0;
/**
* The routes that the message will be sent to
*/
private List routes = new ArrayList();
/**
* Whether or not {@link #initialise()} was already successfully executed
*/
private boolean initialised = false;
/**
* chains built around the routes
*/
private List routeChains;
/**
* The aggregation strategy. By default is this instance
*/
private AggregationStrategy aggregationStrategy;
/**
* the {@link ThreadingProfile} used to create the {@link #workManager}
*/
private ThreadingProfile threadingProfile;
/**
* {@link WorkManager} used to execute the routes in parallel
*/
private WorkManager workManager;
@Override
public MuleEvent process(MuleEvent event) throws MuleException
{
if (CollectionUtils.isEmpty(routes))
{
throw new RoutePathNotFoundException(CoreMessages.noEndpointsForRouter(), event, null);
}
MuleMessage message = event.getMessage();
AbstractRoutingStrategy.validateMessageIsNotConsumable(event, message);
List works = executeWork(event);
MuleEvent response = processResponses(event, works);
if (response instanceof DefaultMuleEvent)
{
// use a copy instead of a resetAccessControl
// to assure that all property changes
// are flushed from the worker thread to this one
response = DefaultMuleEvent.copy(response);
OptimizedRequestContext.unsafeSetEvent(response);
}
return response;
}
private MuleEvent processResponses(MuleEvent event, List works)
throws MuleException
{
List responses = new ArrayList(works.size());
long remainingTimeout = timeout;
for (int routeIndex = 0; routeIndex < works.size(); routeIndex++)
{
MuleEvent response = null;
Exception exception = null;
ProcessingMuleEventWork work = works.get(routeIndex);
MessageProcessor route = routes.get(routeIndex);
long startedAt = System.currentTimeMillis();
try
{
response = work.getResult(remainingTimeout, TimeUnit.MILLISECONDS);
}
catch (ResponseTimeoutException e)
{
exception = e;
}
catch (InterruptedException e)
{
throw new DefaultMuleException(MessageFactory.createStaticMessage(String.format(
"Was interrupted while waiting for route %d", routeIndex)), e);
}
catch (MessagingException e)
{
exception = wrapInDispatchException(e.getEvent(), routeIndex, route, e);
}
catch (Exception e)
{
exception = wrapInDispatchException(event, routeIndex, route, e);
}
remainingTimeout -= System.currentTimeMillis() - startedAt;
if (exception != null)
{
if (logger.isDebugEnabled())
{
logger.debug(
String.format("route %d generated exception for MuleEvent %s", routeIndex,
event.getId()), exception);
}
if (exception instanceof MessagingException)
{
response = DefaultMuleEvent.copy(((MessagingException) exception).getEvent());
}
else
{
response = DefaultMuleEvent.copy(event);
}
if (response.getMessage().getExceptionPayload() == null)
{
response.getMessage().setExceptionPayload(new DefaultExceptionPayload(exception));
}
}
else
{
if (logger.isDebugEnabled())
{
logger.debug(String.format("route %d executed successfully for event %s", routeIndex,
event.getId()));
}
}
responses.add(response);
}
return aggregationStrategy.aggregate(new AggregationContext(event, responses));
}
private Exception wrapInDispatchException(MuleEvent event, int routeIndex, MessageProcessor route, Exception e)
{
return new DispatchException(MessageFactory.createStaticMessage(String.format(
"route number %d failed to be executed", routeIndex)), event, route, e);
}
private List executeWork(MuleEvent event) throws MuleException
{
List works = new ArrayList(routes.size());
try
{
for (final MessageProcessor route : routes)
{
ProcessingMuleEventWork work = new ProcessingMuleEventWork(route, event);
workManager.scheduleWork(work);
works.add(work);
}
}
catch (WorkException e)
{
throw new DefaultMuleException(
MessageFactory.createStaticMessage("Could not schedule work for route"), e);
}
return works;
}
@Override
public void initialise() throws InitialisationException
{
try
{
buildRouteChains();
if (threadingProfile == null)
{
threadingProfile = muleContext.getDefaultThreadingProfile();
}
if (aggregationStrategy == null)
{
aggregationStrategy = new CollectAllAggregationStrategy();
}
if (timeout <= 0)
{
timeout = Long.MAX_VALUE;
}
if (threadingProfile.isDoThreading())
{
workManager = threadingProfile.createWorkManager(
ThreadNameHelper.getPrefix(muleContext) + "ScatterGatherWorkManager",
muleContext.getConfiguration().getShutdownTimeout());
}
else
{
workManager = new SerialWorkManager();
}
}
catch (Exception e)
{
throw new InitialisationException(e, this);
}
super.initialise();
initialised = true;
}
@Override
public void start() throws MuleException
{
workManager.start();
super.start();
}
@Override
public void dispose()
{
try
{
workManager.dispose();
}
catch (Exception e)
{
logger.error(
"Exception found while tring to dispose work manager. Will continue with the disposal", e);
}
finally
{
super.dispose();
}
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException if invoked after {@link #initialise()} is
* completed
*/
@Override
public void addRoute(MessageProcessor processor) throws MuleException
{
checkNotInitialised();
routes.add(processor);
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException if invoked after {@link #initialise()} is
* completed
*/
@Override
public void removeRoute(MessageProcessor processor) throws MuleException
{
checkNotInitialised();
routes.remove(processor);
}
private void buildRouteChains() throws MuleException
{
Preconditions.checkState(routes.size() > 1, "At least 2 routes are required for ScatterGather");
routeChains = new ArrayList(routes.size());
for (MessageProcessor route : routes)
{
if (route instanceof MessageProcessorChain)
{
routeChains.add(route);
}
else
{
routeChains.add(new DefaultMessageProcessorChainBuilder().chain(route).build());
}
}
}
private void checkNotInitialised()
{
Preconditions.checkState(initialised == false,
" router is not dynamic. Cannot modify routes after initialisation");
}
@Override
protected List getOwnedMessageProcessors()
{
return routeChains;
}
public void setAggregationStrategy(AggregationStrategy aggregationStrategy)
{
this.aggregationStrategy = aggregationStrategy;
}
public void setThreadingProfile(ThreadingProfile threadingProfile)
{
this.threadingProfile = threadingProfile;
}
public void setTimeout(long timeout)
{
this.timeout = timeout;
}
public void setRoutes(List routes)
{
this.routes = routes;
}
}