Package org.mule.routing.correlation

Source Code of org.mule.routing.correlation.EventCorrelator$ExpiringGroupMonitoringThread

/*
* 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.correlation;

import org.mule.api.MessagingException;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessageCollection;
import org.mule.api.config.MuleProperties;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.execution.ExecutionCallback;
import org.mule.api.execution.ExecutionTemplate;
import org.mule.api.lifecycle.Disposable;
import org.mule.api.lifecycle.Startable;
import org.mule.api.lifecycle.Stoppable;
import org.mule.api.processor.MessageProcessor;
import org.mule.api.routing.MessageInfoMapping;
import org.mule.api.routing.RoutingException;
import org.mule.api.service.Service;
import org.mule.api.store.ListableObjectStore;
import org.mule.api.store.ObjectAlreadyExistsException;
import org.mule.api.store.ObjectDoesNotExistException;
import org.mule.api.store.ObjectStore;
import org.mule.api.store.ObjectStoreException;
import org.mule.api.store.ObjectStoreManager;
import org.mule.config.i18n.CoreMessages;
import org.mule.context.notification.RoutingNotification;
import org.mule.execution.ErrorHandlingExecutionTemplate;
import org.mule.routing.EventGroup;
import org.mule.routing.EventProcessingThread;
import org.mule.util.StringMessageUtils;
import org.mule.util.concurrent.ThreadNameHelper;
import org.mule.util.monitor.Expirable;
import org.mule.util.monitor.ExpiryMonitor;
import org.mule.util.store.DeserializationPostInitialisable;

import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class EventCorrelator implements Startable, Stoppable, Disposable
{

    /**
     * logger used by this class
     */
    protected transient final Log logger = LogFactory.getLog(EventCorrelator.class);

    public static final String NO_CORRELATION_ID = "no-id";

    public static final int MAX_PROCESSED_GROUPS = 50000;

    private static final long ONE_DAY_IN_MILLI = 1000 * 60 * 60 * 24;

    protected long groupTimeToLive = ONE_DAY_IN_MILLI;

    /**
     * A map of EventGroup objects. These represent one or more messages to be
     * agregated, keyed by message id. There will be one response message for every
     * EventGroup.
     */
    protected ListableObjectStore<EventGroup> eventGroups;

    protected final Object groupsLock = new Object();

    // @GuardedBy groupsLock
    protected ObjectStore<Long> processedGroups = null;

    private long timeout = -1; // undefined

    private boolean failOnTimeout = true;

    private MessageInfoMapping messageInfoMapping;

    private MuleContext muleContext;

    private EventCorrelatorCallback callback;

    private MessageProcessor timeoutMessageProcessor;

    /**
     * A map with keys = group id and values = group creation time
     */
    private ListableObjectStore<Long> expiredAndDispatchedGroups = null;

    private EventCorrelator.ExpiringGroupMonitoringThread expiringGroupMonitoringThread;
    private final String name;

    private final boolean persistentStores;
    private final String storePrefix;
    private final FlowConstruct flowConstruct;

    public EventCorrelator(EventCorrelatorCallback callback,
                           MessageProcessor timeoutMessageProcessor,
                           MessageInfoMapping messageInfoMapping,
                           MuleContext muleContext,
                           FlowConstruct flowConstruct,
                           boolean persistentStores,
                           String storePrefix)
    {
        if (callback == null)
        {
            throw new IllegalArgumentException(CoreMessages.objectIsNull("EventCorrelatorCallback")
                                                       .getMessage());
        }
        if (messageInfoMapping == null)
        {
            throw new IllegalArgumentException(CoreMessages.objectIsNull("MessageInfoMapping").getMessage());
        }
        if (muleContext == null)
        {
            throw new IllegalArgumentException(CoreMessages.objectIsNull("MuleContext").getMessage());
        }
        this.callback = callback;
        this.messageInfoMapping = messageInfoMapping;
        this.muleContext = muleContext;
        this.timeoutMessageProcessor = timeoutMessageProcessor;
        this.persistentStores = persistentStores;
        this.storePrefix = storePrefix;
        name = String.format("%s%s.event.correlator", ThreadNameHelper.getPrefix(muleContext),
                             flowConstruct.getName());
        ObjectStoreManager objectStoreManager = muleContext.getRegistry().get(
                MuleProperties.OBJECT_STORE_MANAGER);
        expiredAndDispatchedGroups = (ListableObjectStore<Long>) objectStoreManager.getObjectStore(
                storePrefix + ".expiredAndDispatchedGroups", persistentStores);
        processedGroups = (ListableObjectStore<Long>) objectStoreManager.getObjectStore(storePrefix
                                                                                        + ".processedGroups",
                                                                                        persistentStores, MAX_PROCESSED_GROUPS, -1, 1000);
        eventGroups = (ListableObjectStore<EventGroup>) objectStoreManager.getObjectStore(storePrefix
                                                                                          + ".eventGroups",
                                                                                          persistentStores);
        this.flowConstruct = flowConstruct;
    }

    public void forceGroupExpiry(String groupId) throws MessagingException
    {
        try
        {
            if (eventGroups.retrieve(groupId) != null)
            {
                handleGroupExpiry(getEventGroup(groupId));
            }
            else
            {
                addProcessedGroup(groupId);
            }
        }
        catch (ObjectStoreException e)
        {
            // TODO improve this
            throw new MessagingException(null, e);
        }
    }

    public MuleEvent process(MuleEvent event) throws RoutingException
    {
        // the correlationId of the event's message
        final String groupId = messageInfoMapping.getCorrelationId(event.getMessage());

        if (logger.isTraceEnabled())
        {
            try
            {
                logger.trace(String.format("Received async reply message for correlationID: %s%n%s%n%s",
                                           groupId, StringMessageUtils.truncate(
                        StringMessageUtils.toString(event.getMessage().getPayload()), 200, false),
                                           StringMessageUtils.headersToString(event.getMessage())));
            }
            catch (Exception e)
            {
                // ignore
            }
        }
        if (groupId == null || groupId.equals("-1"))
        {
            throw new RoutingException(CoreMessages.noCorrelationId(), event, timeoutMessageProcessor);
        }

        // spinloop for the EventGroup lookup
        while (true)
        {
            try
            {
                if (isGroupAlreadyProcessed(groupId))
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug("An event was received for an event group that has already been processed, "
                                     + "this is probably because the async-reply timed out. Correlation Id is: "
                                     + groupId + ". Dropping event");
                    }
                    // Fire a notification to say we received this message
                    muleContext.fireNotification(new RoutingNotification(event.getMessage(),
                                                                         event.getMessageSourceURI().toString(),
                                                                         RoutingNotification.MISSED_AGGREGATION_GROUP_EVENT));
                    return null;
                }
            }
            catch (ObjectStoreException e)
            {
                throw new RoutingException(event, timeoutMessageProcessor, e);
            }

            // check for an existing group first
            EventGroup group;
            try
            {
                group = this.getEventGroup(groupId);
            }
            catch (ObjectStoreException e)
            {
                throw new RoutingException(event, timeoutMessageProcessor, e);
            }

            // does the group exist?
            if (group == null)
            {
                // ..apparently not, so create a new one & add it
                try
                {
                    group = this.addEventGroup(callback.createEventGroup(event, groupId));
                }
                catch (ObjectStoreException e)
                {
                    throw new RoutingException(event, timeoutMessageProcessor, e);
                }
            }

            // ensure that only one thread at a time evaluates this EventGroup
            synchronized (groupsLock)
            {
                if (logger.isDebugEnabled())
                {
                    logger.debug("Adding event to aggregator group: " + groupId);
                }

                // add the incoming event to the group
                try
                {
                    group.addEvent(event);
                }
                catch (ObjectStoreException e)
                {
                    throw new RoutingException(event, timeoutMessageProcessor, e);
                }

                // check to see if the event group is ready to be aggregated
                if (callback.shouldAggregateEvents(group))
                {
                    // create the response event
                    MuleEvent returnEvent = callback.aggregateEvents(group);
                    returnEvent.getMessage().setCorrelationId(groupId);
                    String rootId = group.getCommonRootId();
                    if (rootId != null)
                    {
                        returnEvent.getMessage().setMessageRootId(rootId);
                    }

                    // remove the eventGroup as no further message will be received
                    // for this group once we aggregate
                    try
                    {
                        this.removeEventGroup(group);
                        group.clear();
                    }
                    catch (ObjectStoreException e)
                    {
                        throw new RoutingException(event, timeoutMessageProcessor, e);
                    }

                    return returnEvent;
                }
                else
                {
                    return null;
                }
            }
        }
    }

    protected EventGroup getEventGroup(Serializable groupId) throws ObjectStoreException
    {
        try
        {
            EventGroup eventGroup = eventGroups.retrieve(groupId);
            if (!eventGroup.isInitialised())
            {
                try
                {
                    DeserializationPostInitialisable.Implementation.init(eventGroup, muleContext);
                }
                catch (Exception e)
                {
                    throw new ObjectStoreException(e);
                }
            }
            return eventGroup;
        }
        catch (ObjectDoesNotExistException e)
        {
            return null;
        }
    }

    protected EventGroup addEventGroup(EventGroup group) throws ObjectStoreException
    {
        try
        {
            eventGroups.store((Serializable) group.getGroupId(), group);
            return group;
        }
        catch (ObjectAlreadyExistsException e)
        {
            return getEventGroup((String) group.getGroupId());
        }
    }

    protected void removeEventGroup(EventGroup group) throws ObjectStoreException
    {
        final Object groupId = group.getGroupId();
        eventGroups.remove((Serializable) groupId);
        addProcessedGroup(groupId);
    }

    protected void addProcessedGroup(Object id) throws ObjectStoreException
    {
        synchronized (groupsLock)
        {
            processedGroups.store((Serializable) id, System.currentTimeMillis());
        }
    }

    protected boolean isGroupAlreadyProcessed(Object id) throws ObjectStoreException
    {
        synchronized (groupsLock)
        {
            return processedGroups.contains((Serializable) id);
        }
    }

    public boolean isFailOnTimeout()
    {
        return failOnTimeout;
    }

    public void setFailOnTimeout(boolean failOnTimeout)
    {
        this.failOnTimeout = failOnTimeout;
    }

    public long getTimeout()
    {
        return timeout;
    }

    public void setTimeout(long timeout)
    {
        this.timeout = timeout;
    }

    protected void handleGroupExpiry(EventGroup group) throws MessagingException
    {
        try
        {
            removeEventGroup(group);
        }
        catch (ObjectStoreException e)
        {
            throw new MessagingException(group.getMessageCollectionEvent(), e);
        }

        if (isFailOnTimeout())
        {
            MuleMessageCollection messageCollection;
            try
            {
                messageCollection = group.toMessageCollection();
            }
            catch (ObjectStoreException e)
            {
                throw new MessagingException(group.getMessageCollectionEvent(), e);
            }
            muleContext.fireNotification(new RoutingNotification(messageCollection, null,
                                                                 RoutingNotification.CORRELATION_TIMEOUT));
            MuleEvent groupCollectionEvent = group.getMessageCollectionEvent();
            try
            {
                group.clear();
            }
            catch (ObjectStoreException e)
            {
                logger.warn("Failed to clear group with id " + group.getGroupId()
                            + " since underlying ObjectStore threw Exception:" + e.getMessage());
            }
            throw new CorrelationTimeoutException(CoreMessages.correlationTimedOut(group.getGroupId()),
                                                  groupCollectionEvent);
        }
        else
        {
            if (logger.isDebugEnabled())
            {
                logger.debug(MessageFormat.format(
                        "Aggregator expired, but ''failOnTimeOut'' is false. Forwarding {0} events out of {1} "
                        + "total for group ID: {2}", group.size(), group.expectedSize(),
                        group.getGroupId()));
            }

            try
            {
                if (!(group.getCreated() + groupTimeToLive < System.currentTimeMillis()))
                {
                    MuleEvent newEvent = callback.aggregateEvents(group);
                    group.clear();
                    newEvent.getMessage().setCorrelationId(group.getGroupId().toString());

                    if (!expiredAndDispatchedGroups.contains((Serializable) group.getGroupId()))
                    {
                        // TODO which use cases would need a sync reply event
                        // returned?
                        if (timeoutMessageProcessor != null)
                        {
                            timeoutMessageProcessor.process(newEvent);
                        }
                        else
                        {
                            final FlowConstruct service = group.toArray(false)[0].getFlowConstruct();
                            if (!(service instanceof Service))
                            {
                                throw new UnsupportedOperationException(
                                        "EventAggregator is only supported with Service");
                            }

                            ((Service) service).dispatchEvent(newEvent);
                        }
                        expiredAndDispatchedGroups.store((Serializable) group.getGroupId(),
                                                         group.getCreated());
                    }
                    else
                    {
                        logger.warn(MessageFormat.format("Discarding group {0}", group.getGroupId()));
                    }
                }
            }
            catch (MessagingException me)
            {
                throw me;
            }
            catch (Exception e)
            {
                throw new MessagingException(group.getMessageCollectionEvent(), e);
            }
        }
    }

    @Override
    public void start() throws MuleException
    {
        logger.info("Starting event correlator: " + name);
        if (timeout != 0)
        {
            expiringGroupMonitoringThread = new ExpiringGroupMonitoringThread();
            expiringGroupMonitoringThread.start();
        }
    }

    @Override
    public void stop() throws MuleException
    {
        logger.info("Stopping event correlator: " + name);
        if (expiringGroupMonitoringThread != null)
        {
            expiringGroupMonitoringThread.stopProcessing();
        }
    }

    private final class ExpiringGroupMonitoringThread extends EventProcessingThread implements Expirable, Disposable
    {

        private ExpiryMonitor expiryMonitor;
        public static final long DELAY_TIME = 10;

        public ExpiringGroupMonitoringThread()
        {
            super(name, DELAY_TIME);
            this.expiryMonitor = new ExpiryMonitor(name, 1000 * 60, muleContext, true);
            // clean up every 30 minutes
            this.expiryMonitor.addExpirable(1000 * 60 * 30, TimeUnit.MILLISECONDS, this);
        }

        /**
         * Removes the elements in expiredAndDispatchedGroups when groupLife is
         * reached
         *
         * @throws ObjectStoreException
         */
        @Override
        public void expired()
        {
            try
            {
                for (Serializable o : expiredAndDispatchedGroups.allKeys())
                {
                    Long time = expiredAndDispatchedGroups.retrieve(o);
                    if (time + groupTimeToLive < System.currentTimeMillis())
                    {
                        expiredAndDispatchedGroups.remove(o);
                        logger.warn(MessageFormat.format("Discarding group {0}", o));
                    }
                }
            }
            catch (ObjectStoreException e)
            {
                logger.warn("Expiration of objects failed due to ObjectStoreException " + e + ".");
            }
        }

        @Override
        public void doRun()
        {

            ////TODO(pablo.kraan): is not good to have threads doing nothing in all the nodes but the primary. Need to
            ////start the thread on the primary node only, and then use a notification schema to start a new thread
            ////in a different node when the primary goes down.
            if (!muleContext.isPrimaryPollingInstance())
            {
                return;
            }

            List<EventGroup> expired = new ArrayList<EventGroup>(1);
            try
            {
                for (Serializable o : eventGroups.allKeys())
                {
                    EventGroup group = getEventGroup(o);
                    if (group.getCreated() + getTimeout() < System.currentTimeMillis())
                    {
                        expired.add(group);
                    }
                }
            }
            catch (ObjectStoreException e)
            {
                logger.warn("expiry failed dues to ObjectStoreException " + e);
            }
            if (expired.size() > 0)
            {
                for (Object anExpired : expired)
                {
                    final EventGroup group = (EventGroup) anExpired;
                    ExecutionTemplate<MuleEvent> executionTemplate = ErrorHandlingExecutionTemplate.createErrorHandlingExecutionTemplate(muleContext, flowConstruct.getExceptionListener());
                    try
                    {
                        executionTemplate.execute(new ExecutionCallback<MuleEvent>()
                        {
                            @Override
                            public MuleEvent process() throws Exception
                            {
                                handleGroupExpiry(group);
                                return null;
                            }
                        });
                    }
                    catch (MessagingException e)
                    {
                        //Already handled by TransactionTemplate
                    }
                    catch (Exception e)
                    {
                        muleContext.getExceptionListener().handleException(e);
                    }
                }
            }
        }

        @Override
        public void dispose()
        {
            if (expiryMonitor != null)
            {
                expiryMonitor.dispose();
            }
        }
    }

    public void dispose()
    {
        disposeIfDisposable(expiredAndDispatchedGroups);
        disposeIfDisposable(processedGroups);
        disposeIfDisposable(eventGroups);
        disposeIfDisposable(expiringGroupMonitoringThread);
    }

    private void disposeIfDisposable(Object o)
    {
        if (o != null && o instanceof Disposable)
        {
            ((Disposable) o).dispose();
        }
    }
}
TOP

Related Classes of org.mule.routing.correlation.EventCorrelator$ExpiringGroupMonitoringThread

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.