com.mirth.connect.donkey.server.channel.DestinationChain.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.donkey.server.channel.DestinationChain.java

Source

/*
 * Copyright (c) Mirth Corporation. All rights reserved.
 * 
 * http://www.mirthcorp.com
 * 
 * The software in this package is published under the terms of the MPL license a copy of which has
 * been included with this distribution in the LICENSE.txt file.
 */

package com.mirth.connect.donkey.server.channel;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.mirth.connect.donkey.model.message.ConnectorMessage;
import com.mirth.connect.donkey.model.message.ContentType;
import com.mirth.connect.donkey.model.message.MessageContent;
import com.mirth.connect.donkey.model.message.Status;
import com.mirth.connect.donkey.server.data.DonkeyDao;
import com.mirth.connect.donkey.util.ThreadUtils;

public class DestinationChain implements Callable<List<ConnectorMessage>> {

    private DestinationChainProvider chainProvider;
    private ConnectorMessage message;
    private List<Integer> enabledMetaDataIds = new ArrayList<Integer>();
    private Logger logger = Logger.getLogger(getClass());
    private String name;

    public DestinationChain(DestinationChainProvider chainProvider) {
        this.chainProvider = chainProvider;
        enabledMetaDataIds = new ArrayList<Integer>(chainProvider.getMetaDataIds());
        name = "Destination Chain Thread on " + chainProvider.getChannelId();
    }

    public void setMessage(ConnectorMessage message) {
        this.message = message;
    }

    public List<Integer> getEnabledMetaDataIds() {
        return enabledMetaDataIds;
    }

    public void setEnabledMetaDataIds(List<Integer> enabledMetaDataIds) {
        this.enabledMetaDataIds = enabledMetaDataIds;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public List<ConnectorMessage> call() throws InterruptedException {
        String originalThreadName = Thread.currentThread().getName();
        try {
            Thread.currentThread().setName(name + " < " + originalThreadName);
            return doCall();
        } finally {
            Thread.currentThread().setName(originalThreadName);
        }
    }

    private List<ConnectorMessage> doCall() throws InterruptedException {
        List<ConnectorMessage> messages = new ArrayList<ConnectorMessage>();
        ConnectorMessage message = this.message;
        int startMetaDataId = enabledMetaDataIds.indexOf(message.getMetaDataId());
        boolean stopChain = false;

        /*
         * The message that we're starting with should be associated with one of the destinations in
         * this chain, if it's not, we can't proceed.
         */
        if (startMetaDataId == -1) {
            logger.error("The message's metadata ID for channel " + chainProvider.getChannelId()
                    + " is not in the destination chain's list of enabled metadata IDs");
            return null;
        }

        // loop through each metaDataId in the chain, beginning with startMetaDataId
        for (int i = startMetaDataId; i < enabledMetaDataIds.size() && !stopChain; i++) {
            ThreadUtils.checkInterruptedStatus();
            Integer metaDataId = enabledMetaDataIds.get(i);
            Integer nextMetaDataId = (enabledMetaDataIds.size() > (i + 1)) ? enabledMetaDataIds.get(i + 1) : null;
            ConnectorMessage nextMessage = null;
            DestinationConnector destinationConnector = chainProvider.getDestinationConnectors().get(metaDataId);

            /*
             * TRANSACTION: Process Destination - Insert the custom metadata column data - store the
             * transformed content - store the encoded content - store the sent content (done prior
             * to sending since the sent content would be lost if the message gets queued) - store
             * the raw response content - update the message status to either PENDING or QUEUED - if
             * there is a next destination in the chain, create it's message (done in the next
             * transaction if a response transformer is used)
             */
            DonkeyDao dao = chainProvider.getDaoFactory().getDao();

            try {
                Status previousStatus = message.getStatus();

                try {
                    switch (message.getStatus()) {
                    case RECEIVED:
                        /*
                         * Only transform the message if we're going to be dispatching it in the
                         * main processing thread, or if the queue thread will not be handling
                         * transformation
                         */
                        if (destinationConnector.willAttemptSend()
                                || !destinationConnector.includeFilterTransformerInQueue()) {
                            destinationConnector.transform(dao, message, previousStatus, true);

                            // If the message status is QUEUED, send it to the destination connector
                            if (message.getStatus() == Status.QUEUED) {
                                String originalThreadName = Thread.currentThread().getName();
                                try {
                                    Thread.currentThread()
                                            .setName(destinationConnector.getConnectorProperties().getName()
                                                    + " Process Thread on "
                                                    + destinationConnector.getChannel().getName() + " ("
                                                    + chainProvider.getChannelId() + "), "
                                                    + destinationConnector.getDestinationName() + " (" + metaDataId
                                                    + ")");
                                    destinationConnector.process(dao, message, previousStatus);
                                } finally {
                                    Thread.currentThread().setName(originalThreadName);
                                }
                            } else if (message.getStatus() == Status.ERROR && message.getSent() == null) {
                                // If an error occurred in the filter/transformer, don't proceed with the rest of the chain
                                stopChain = true;
                            }
                        } else {
                            destinationConnector.updateQueuedStatus(dao, message, previousStatus);
                        }
                        break;

                    case PENDING:
                        chainProvider.getDestinationConnectors().get(metaDataId).processPendingConnectorMessage(dao,
                                message);
                        break;

                    case SENT:
                        break;

                    default:
                        // the status should never be anything but one of the above statuses, but in case it's not, log an error
                        logger.error("Received a message with an invalid status in channel "
                                + chainProvider.getChannelId() + ".");
                        break;
                    }
                } catch (RuntimeException e) { // TODO: remove this catch since we can't determine an error code
                    // if an error occurred in processing the message through the current destination, then update the message status to ERROR and continue processing through the chain
                    logger.error("Error processing destination "
                            + chainProvider.getDestinationConnectors().get(metaDataId).getDestinationName()
                            + " for channel " + chainProvider.getChannelId() + ".", e);
                    stopChain = true;
                    dao.rollback();
                    message.setStatus(Status.ERROR);
                    message.setProcessingError(e.toString());
                    dao.updateStatus(message, previousStatus);
                    // Insert errors if necessary
                    if (StringUtils.isNotBlank(message.getProcessingError())) {
                        dao.updateErrors(message);
                    }
                }

                // now that we're finished processing the current message, we can create the next message in the chain
                if (nextMetaDataId != null && !stopChain) {
                    nextMessage = new ConnectorMessage(message.getChannelId(), message.getChannelName(),
                            message.getMessageId(), nextMetaDataId, message.getServerId(), Calendar.getInstance(),
                            Status.RECEIVED);

                    DestinationConnector nextDestinationConnector = chainProvider.getDestinationConnectors()
                            .get(nextMetaDataId);
                    nextMessage.setConnectorName(nextDestinationConnector.getDestinationName());
                    nextMessage.setChainId(chainProvider.getChainId());
                    nextMessage.setOrderId(nextDestinationConnector.getOrderId());

                    // We don't create a new map here because the source map is read-only and thus won't ever be changed
                    nextMessage.setSourceMap(message.getSourceMap());
                    nextMessage.setChannelMap(new HashMap<String, Object>(message.getChannelMap()));
                    nextMessage.setResponseMap(new HashMap<String, Object>(message.getResponseMap()));
                    nextMessage.setRaw(new MessageContent(message.getChannelId(), message.getMessageId(),
                            nextMetaDataId, ContentType.RAW, message.getRaw().getContent(),
                            nextDestinationConnector.getInboundDataType().getType(),
                            message.getRaw().isEncrypted()));

                    ThreadUtils.checkInterruptedStatus();
                    dao.insertConnectorMessage(nextMessage, chainProvider.getStorageSettings().isStoreMaps(), true);
                }

                ThreadUtils.checkInterruptedStatus();

                if (message.getStatus() != Status.QUEUED) {
                    dao.commit(chainProvider.getStorageSettings().isDurable());
                } else {
                    // Block other threads from reading from or modifying the destination queue until both the current commit and queue addition finishes
                    // Otherwise the same message could be sent multiple times.
                    synchronized (destinationConnector.getQueue()) {
                        dao.commit(chainProvider.getStorageSettings().isDurable());

                        if (message.getStatus() == Status.QUEUED) {
                            destinationConnector.getQueue().add(message);
                        }
                    }
                }

                messages.add(message);
            } catch (RuntimeException e) {
                // An exception caught at this point either occurred when attempting to handle an exception in the above try/catch, or when attempting to create the next destination's message, the thread cannot continue running
                logger.error("Error processing destination "
                        + chainProvider.getDestinationConnectors().get(metaDataId).getDestinationName()
                        + " for channel " + chainProvider.getChannelId() + ".", e);
                throw e;
            } finally {
                dao.close();
            }

            // Set the next message in the loop
            if (nextMetaDataId != null) {
                message = nextMessage;
            }
        }

        return messages;
    }
}