Package org.mule.routing

Source Code of org.mule.routing.UntilSuccessful

/*
* $Id: UntilSuccessful.java 22225 2011-06-17 19:22:55Z ddossot $
* --------------------------------------------------------------------------------------
* 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 java.io.Serializable;
import java.util.concurrent.TimeUnit;

import org.mule.DefaultMuleEvent;
import org.mule.DefaultMuleMessage;
import org.mule.api.MessagingException;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.MuleRuntimeException;
import org.mule.api.endpoint.EndpointBuilder;
import org.mule.api.endpoint.EndpointException;
import org.mule.api.endpoint.OutboundEndpoint;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.retry.RetryCallback;
import org.mule.api.retry.RetryContext;
import org.mule.api.retry.RetryNotifier;
import org.mule.api.retry.RetryPolicyTemplate;
import org.mule.api.store.ListableObjectStore;
import org.mule.api.store.ObjectStoreException;
import org.mule.config.i18n.MessageFactory;
import org.mule.retry.async.AsynchronousRetryTemplate;
import org.mule.retry.policies.SimpleRetryPolicyTemplate;
import org.mule.routing.filters.ExpressionFilter;
import org.mule.routing.outbound.AbstractOutboundRouter;

/**
* UntilSuccessful attempts to route a message to the message processor it contains in an asynchronous manner. Routing
* is considered successful if no exception has been raised and, optionally, if the response matches an expression.
* UntilSuccessful can optionally be configured to synchronously return an acknowledgment message when it has scheduled
* the event for processing. UntilSuccessful is backed by a {@link ListableObjectStore} for storing the events that are
* pending (re)processing.
*/
public class UntilSuccessful extends AbstractOutboundRouter
{
    public static class EventStoreKey implements Serializable
    {
        private static final long serialVersionUID = 1L;
        private final String value;

        private EventStoreKey(String value)
        {
            this.value = value;
        }

        public static EventStoreKey buildFor(MuleEvent muleEvent)
        {
            // the key is built in way to prevent UntilSuccessful workers across a cluster to compete for the same
            // events over a shared object store
            return new EventStoreKey(muleEvent.getFlowConstruct() + "@"
                                     + muleEvent.getMuleContext().getConfiguration().getClusterId() + ":"
                                     + muleEvent.getId());
        }

        @Override
        public String toString()
        {
            return value;
        }

        @Override
        public int hashCode()
        {
            return value.hashCode();
        }

        @Override
        public boolean equals(Object obj)
        {
            if (!(obj instanceof EventStoreKey))
            {
                return false;
            }

            return value.equals(((EventStoreKey) obj).value);
        }
    }

    public static final String PROCESS_ATTEMPT_COUNT_PROPERTY_NAME = "process.attempt.count";

    private static final int DEFAULT_PROCESS_ATTEMPT_COUNT_PROPERTY_VALUE = 1;

    private ListableObjectStore<MuleEvent> objectStore;
    private int maxRetries = 5;
    private long secondsBetweenRetries = 60L;
    private String failureExpression;
    private String ackExpression;
    private ExpressionFilter failureExpressionFilter;
    private String eventKeyPrefix;
    private EndpointBuilder dlqEndpointBuilder;
    private OutboundEndpoint dlqEndpoint;

    @Override
    public void initialise() throws InitialisationException
    {
        if (routes.isEmpty())
        {
            throw new InitialisationException(
                MessageFactory.createStaticMessage("One message processor must be configured within UntilSuccessful."),
                this);
        }

        if (routes.size() > 1)
        {
            throw new InitialisationException(
                MessageFactory.createStaticMessage("Only one message processor is allowed within UntilSuccessful."
                                                   + " Use a Processor Chain to group several message processors into one."),
                this);
        }

        if (objectStore == null)
        {
            throw new InitialisationException(
                MessageFactory.createStaticMessage("A ListableObjectStore must be configured on UntilSuccessful."),
                this);
        }

        super.initialise();

        if (dlqEndpointBuilder != null)
        {
            try
            {
                dlqEndpoint = dlqEndpointBuilder.buildOutboundEndpoint();
            }
            catch (EndpointException ee)
            {
                throw new InitialisationException(
                    MessageFactory.createStaticMessage("Invalid DQL endpoint builder: " + dlqEndpointBuilder),
                    ee, this);
            }
        }

        if (failureExpression != null)
        {
            failureExpressionFilter = new ExpressionFilter(failureExpression);
        }
        else
        {
            failureExpressionFilter = new ExpressionFilter("exception-type:");
        }
        failureExpressionFilter.setMuleContext(muleContext);

        if ((ackExpression != null) && (!muleContext.getExpressionManager().isExpression(ackExpression)))
        {
            throw new InitialisationException(MessageFactory.createStaticMessage("Invalid ackExpression: "
                                                                                 + ackExpression), this);
        }

        eventKeyPrefix = flowConstruct.getName() + "@" + muleContext.getConfiguration().getClusterId() + ":";
    }

    @Override
    public void start() throws MuleException
    {
        super.start();
        scheduleAllPendingEventsForProcessing();
    }

    @Override
    public boolean isMatch(MuleMessage message) throws MuleException
    {
        return true;
    }

    @Override
    protected MuleEvent route(final MuleEvent event) throws MessagingException
    {
        try
        {
            ensurePayloadSerializable(event);
        }
        catch (final Exception e)
        {
            throw new MessagingException(
                MessageFactory.createStaticMessage("Failed to prepare message for processing"), event, e);
        }

        try
        {
            EventStoreKey eventStoreKey = storeEvent(event);
            scheduleForProcessing(eventStoreKey);

            if (ackExpression == null)
            {
                return null;
            }

            Object ackResponsePayload = muleContext.getExpressionManager().evaluate(ackExpression,
                event.getMessage());

            return new DefaultMuleEvent(new DefaultMuleMessage(ackResponsePayload, event.getMessage(),
                muleContext), event);
        }
        catch (Exception e)
        {
            throw new MessagingException(
                MessageFactory.createStaticMessage("Failed to schedule the event for processing"), event, e);
        }
    }

    private void scheduleAllPendingEventsForProcessing() throws ObjectStoreException
    {
        for (Serializable eventStoreKey : objectStore.allKeys())
        {
            try
            {
                scheduleForProcessing((EventStoreKey) eventStoreKey);
            }
            catch (Exception e)
            {
                logger.error(
                    MessageFactory.createStaticMessage("Failed to schedule for processing event stored with key: "
                                                       + eventStoreKey), e);
            }
        }
    }

    private void scheduleForProcessing(final EventStoreKey eventStoreKey) throws Exception
    {
        RetryCallback callback = new RetryCallback()
        {
            @Override
            public String getWorkDescription()
            {
                return "Until successful processing of event stored under key: " + eventStoreKey;
            }

            @Override
            public void doWork(RetryContext context) throws Exception
            {
                retrieveAndProcessEvent(eventStoreKey);
            }
        };

        SimpleRetryPolicyTemplate simpleRetryPolicyTemplate = new SimpleRetryPolicyTemplate(
            TimeUnit.SECONDS.toMillis(secondsBetweenRetries), maxRetries);

        RetryPolicyTemplate retryPolicyTemplate = new AsynchronousRetryTemplate(simpleRetryPolicyTemplate);
        retryPolicyTemplate.setNotifier(new RetryNotifier()
        {
            @Override
            public void onSuccess(RetryContext context)
            {
                removeFromStore(eventStoreKey);
            }

            @Override
            public void onFailure(RetryContext context, Throwable e)
            {
                incrementProcessAttemptCountOrRemoveFromStore(eventStoreKey);
            }
        });

        retryPolicyTemplate.execute(callback, muleContext.getWorkManager());
    }

    private EventStoreKey storeEvent(final MuleEvent event) throws ObjectStoreException
    {
        MuleMessage message = event.getMessage();
        Integer deliveryAttemptCount = message.getInvocationProperty(PROCESS_ATTEMPT_COUNT_PROPERTY_NAME,
            DEFAULT_PROCESS_ATTEMPT_COUNT_PROPERTY_VALUE);
        return storeEvent(event, deliveryAttemptCount);
    }

    private EventStoreKey storeEvent(final MuleEvent event, int deliveryAttemptCount)
        throws ObjectStoreException
    {
        final MuleMessage message = event.getMessage();
        message.setInvocationProperty(PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, deliveryAttemptCount);
        EventStoreKey eventStoreKey = EventStoreKey.buildFor(event);
        objectStore.store(eventStoreKey, event);
        return eventStoreKey;
    }

    private void incrementProcessAttemptCountOrRemoveFromStore(final EventStoreKey eventStoreKey)
    {
        try
        {
            MuleEvent event = objectStore.remove(eventStoreKey);
            MuleEvent mutableEvent = threadSafeCopy(event);

            final MuleMessage message = mutableEvent.getMessage();
            final Integer deliveryAttemptCount = message.getInvocationProperty(
                PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, DEFAULT_PROCESS_ATTEMPT_COUNT_PROPERTY_VALUE);

            if (deliveryAttemptCount <= getMaxRetries())
            {
                // we store the incremented version unless the max attempt count has been reached
                message.setInvocationProperty(PROCESS_ATTEMPT_COUNT_PROPERTY_NAME, deliveryAttemptCount + 1);
                objectStore.store(eventStoreKey, mutableEvent);
            }
            else
            {
                abandonRetries(event, mutableEvent);
            }
        }
        catch (ObjectStoreException ose)
        {
            logger.error("Failed to increment failure count for event stored with key: " + eventStoreKey);
        }
    }

    private void abandonRetries(MuleEvent event, MuleEvent mutableEvent)
    {
        if (dlqEndpoint == null)
        {
            logger.info("Retry attempts exhausted and no DLQ endpoint defined, dropping message: " + event);
            return;
        }

        try
        {
            logger.info("Retry attempts exhausted, routing message to DLQ endpoint: " + dlqEndpoint);
            dlqEndpoint.process(mutableEvent);
        }
        catch (MuleException me)
        {
            logger.error("Failed to route message to DLQ endpoint: " + dlqEndpoint + ", dropping message: "
                         + event, me);
        }
    }

    private void removeFromStore(final EventStoreKey eventStoreKey)
    {
        try
        {
            objectStore.remove(eventStoreKey);
        }
        catch (ObjectStoreException ose)
        {
            logger.warn("Failed to remove following event from store with key: " + eventStoreKey);
        }
    }

    private void retrieveAndProcessEvent(EventStoreKey eventStoreKey) throws ObjectStoreException
    {
        MuleEvent persistedEvent = objectStore.retrieve(eventStoreKey);
        MuleEvent mutableEvent = threadSafeCopy(persistedEvent);
        processEvent(mutableEvent);
    }

    private void processEvent(MuleEvent event)
    {
        if (routes.isEmpty())
        {
            return;
        }

        MuleEvent returnEvent;
        try
        {
            returnEvent = routes.get(0).process(event);
        }
        catch (MuleException me)
        {
            throw new MuleRuntimeException(me);
        }

        if (returnEvent == null)
        {
            return;
        }

        MuleMessage msg = returnEvent.getMessage();
        if (msg == null)
        {
            throw new MuleRuntimeException(
                MessageFactory.createStaticMessage("No message found in response to processing, which is therefore considered failed for event: "
                                                   + event));
        }

        boolean errorDetected = failureExpressionFilter.accept(msg);
        if (errorDetected)
        {
            throw new MuleRuntimeException(
                MessageFactory.createStaticMessage("Failure expression positive when processing event: "
                                                   + event));
        }
    }

    private DefaultMuleEvent threadSafeCopy(MuleEvent event)
    {
        return new DefaultMuleEvent(new DefaultMuleMessage(event.getMessage()), event);
    }

    private void ensurePayloadSerializable(final MuleEvent event) throws Exception
    {
        final MuleMessage message = event.getMessage();
        if (message instanceof DefaultMuleMessage)
        {
            if (((DefaultMuleMessage) message).isConsumable())
            {
                message.getPayloadAsBytes();
            }
        }
        else
        {
            message.getPayloadAsBytes();
        }
    }

    public ListableObjectStore<MuleEvent> getObjectStore()
    {
        return objectStore;
    }

    public void setObjectStore(final ListableObjectStore<MuleEvent> objectStore)
    {
        this.objectStore = objectStore;
    }

    public int getMaxRetries()
    {
        return maxRetries;
    }

    public void setMaxRetries(int maxRetries)
    {
        this.maxRetries = maxRetries;
    }

    public long getSecondsBetweenRetries()
    {
        return secondsBetweenRetries;
    }

    public void setSecondsBetweenRetries(long secondsBetweenRetries)
    {
        this.secondsBetweenRetries = secondsBetweenRetries;
    }

    public String getFailureExpression()
    {
        return failureExpression;
    }

    public void setFailureExpression(String failureExpression)
    {
        this.failureExpression = failureExpression;
    }

    public String getAckExpression()
    {
        return ackExpression;
    }

    public void setAckExpression(String ackExpression)
    {
        this.ackExpression = ackExpression;
    }

    public void setDlqEndpoint(EndpointBuilder dlqEndpointBuilder)
    {
        this.dlqEndpointBuilder = dlqEndpointBuilder;
    }

    public EndpointBuilder getDlqEndpoint()
    {
        return dlqEndpointBuilder;
    }

    public String getEventKeyPrefix()
    {
        return eventKeyPrefix;
    }

    public ExpressionFilter getFailureExpressionFilter()
    {
        return failureExpressionFilter;
    }
}
TOP

Related Classes of org.mule.routing.UntilSuccessful

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.