com.mirth.connect.connectors.jms.JmsDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.connectors.jms.JmsDispatcher.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.connectors.jms;

import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.log4j.Logger;

import com.mirth.connect.donkey.model.channel.ConnectorProperties;
import com.mirth.connect.donkey.model.event.ConnectionStatusEventType;
import com.mirth.connect.donkey.model.event.ErrorEventType;
import com.mirth.connect.donkey.model.message.ConnectorMessage;
import com.mirth.connect.donkey.model.message.Response;
import com.mirth.connect.donkey.model.message.Status;
import com.mirth.connect.donkey.server.ConnectorTaskException;
import com.mirth.connect.donkey.server.channel.DestinationConnector;
import com.mirth.connect.donkey.server.event.ConnectionStatusEvent;
import com.mirth.connect.donkey.server.event.ErrorEvent;
import com.mirth.connect.server.controllers.ChannelController;
import com.mirth.connect.server.controllers.ContextFactoryController;
import com.mirth.connect.server.controllers.ControllerFactory;
import com.mirth.connect.server.controllers.EventController;
import com.mirth.connect.server.util.TemplateValueReplacer;
import com.mirth.connect.server.util.javascript.MirthContextFactory;
import com.mirth.connect.util.BeanUtil;
import com.mirth.connect.util.ErrorMessageBuilder;

public class JmsDispatcher extends DestinationConnector {
    private JmsDispatcherProperties connectorProperties;
    private TemplateValueReplacer replacer = new TemplateValueReplacer();
    private EventController eventController = ControllerFactory.getFactory().createEventController();
    private ContextFactoryController contextFactoryController = ControllerFactory.getFactory()
            .createContextFactoryController();
    private Logger logger = Logger.getLogger(getClass());

    private Map<String, JmsConnection> jmsConnections = new ConcurrentHashMap<String, JmsConnection>();
    private static final int maxConnections = 1000;

    @Override
    public void onDeploy() throws ConnectorTaskException {
        connectorProperties = (JmsDispatcherProperties) getConnectorProperties();
        eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(),
                getDestinationName(), ConnectionStatusEventType.IDLE));
    }

    @Override
    public void onUndeploy() throws ConnectorTaskException {
    }

    @Override
    public void onStart() throws ConnectorTaskException {
    }

    @Override
    public void onStop() throws ConnectorTaskException {
        ConnectorTaskException firstCause = null;

        // Close the JMS connections
        for (String connectorKey : jmsConnections.keySet().toArray(new String[jmsConnections.size()])) {
            try {
                closeJmsConnection(connectorKey);
            } catch (Exception e) {
                if (firstCause == null) {
                    firstCause = new ConnectorTaskException(
                            "Error closing JMS connection (" + connectorProperties.getName() + " \""
                                    + getDestinationName() + "\" on channel " + getChannelId() + ").",
                            firstCause);
                }
            }
        }

        if (firstCause != null) {
            throw firstCause;
        }
    }

    @Override
    public void onHalt() throws ConnectorTaskException {
        onStop();
    }

    @Override
    public void replaceConnectorProperties(ConnectorProperties connectorProperties, ConnectorMessage message) {
        JmsDispatcherProperties jmsDispatcherProperties = (JmsDispatcherProperties) connectorProperties;
        jmsDispatcherProperties.setTemplate(replacer.replaceValues(jmsDispatcherProperties.getTemplate(), message));
        jmsDispatcherProperties
                .setDestinationName(replacer.replaceValues(jmsDispatcherProperties.getDestinationName(), message));
        jmsDispatcherProperties.setConnectionProperties(
                replacer.replaceValuesInMap(jmsDispatcherProperties.getConnectionProperties(), message));
        jmsDispatcherProperties.setUsername(replacer.replaceValues(jmsDispatcherProperties.getUsername(), message));
        jmsDispatcherProperties.setPassword(replacer.replaceValues(jmsDispatcherProperties.getPassword(), message));
        jmsDispatcherProperties.setClientId(replacer.replaceValues(jmsDispatcherProperties.getClientId(), message));

        if (jmsDispatcherProperties.isUseJndi()) {
            jmsDispatcherProperties.setJndiProviderUrl(
                    replacer.replaceValues(jmsDispatcherProperties.getJndiProviderUrl(), message));
            jmsDispatcherProperties.setJndiInitialContextFactory(
                    replacer.replaceValues(jmsDispatcherProperties.getJndiInitialContextFactory(), message));
            jmsDispatcherProperties.setJndiConnectionFactoryName(
                    replacer.replaceValues(jmsDispatcherProperties.getJndiConnectionFactoryName(), message));
        } else {
            jmsDispatcherProperties.setConnectionFactoryClass(
                    replacer.replaceValues(jmsDispatcherProperties.getConnectionFactoryClass(), message));
        }
    }

    @Override
    public Response send(ConnectorProperties connectorProperties, ConnectorMessage connectorMessage)
            throws InterruptedException {
        eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(),
                getDestinationName(), ConnectionStatusEventType.SENDING));
        JmsDispatcherProperties jmsDispatcherProperties = (JmsDispatcherProperties) connectorProperties;

        String responseError = null;
        String responseStatusMessage = "Message sent successfully.";
        Status responseStatus = Status.SENT; // Always set the status to QUEUED

        // Only one connection is allowed to be created per message so keep track of whether a connection was created.
        boolean connectionCreated = false;

        long dispatcherId = getDispatcherId();
        String connectionKey = getConnectionKey(jmsDispatcherProperties);

        // Retrieve the connection from the cache
        JmsConnection jmsConnection = jmsConnections.get(connectionKey);

        try {
            try {
                if (jmsConnection == null) {
                    /*
                     * If the connection was not in the cache, create it and indicate that a
                     * connection was created for this message.
                     */
                    connectionCreated = true;
                    jmsConnection = getJmsConnection(jmsDispatcherProperties, connectionKey, dispatcherId, false);
                }

                // Retrieve the session for this dispatcherId 
                JmsSession jmsSession = getJmsSession(jmsConnection, dispatcherId);

                /*
                 * Get the destination, create the text message, and send it.
                 */
                jmsSession.getProducer().send(
                        getDestination(jmsDispatcherProperties, jmsSession, jmsConnection.getInitialContext()),
                        jmsSession.getSession().createTextMessage(jmsDispatcherProperties.getTemplate()));
            } catch (Exception e) {
                if (!connectionCreated) {
                    /*
                     * If a connection was not already created for this attempt, create a new
                     * connection and attempt to send the message again. This would typically occur
                     * if a connection was lost prior to the message being sent.
                     */
                    try {
                        jmsConnection = getJmsConnection(jmsDispatcherProperties, connectionKey, dispatcherId,
                                true);

                        JmsSession jmsSession = getJmsSession(jmsConnection, dispatcherId);

                        jmsSession.getProducer().send(
                                getDestination(jmsDispatcherProperties, jmsSession,
                                        jmsConnection.getInitialContext()),
                                jmsSession.getSession().createTextMessage(jmsDispatcherProperties.getTemplate()));
                    } catch (Exception e2) {
                        // If the message fails to send again, throw the exception which will set the response status to ERROR
                        throw e2;
                    }
                } else {
                    // Otherwise throw the exception which will set the response status to ERROR
                    throw e;
                }
            }
        } catch (Exception e) {
            String logMessage = "An error occurred in channel \""
                    + ChannelController.getInstance().getDeployedChannelById(getChannelId()).getName() + "\": "
                    + e.getMessage();
            if (isQueueEnabled()) {
                logger.warn(logMessage, ExceptionUtils.getRootCause(e));
            } else {
                logger.error(logMessage, ExceptionUtils.getRootCause(e));
            }

            eventController.dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(),
                    connectorMessage.getMessageId(), ErrorEventType.DESTINATION_CONNECTOR, getDestinationName(),
                    connectorProperties.getName(), "Error occurred when attempting to send JMS message.", e));
            responseStatus = Status.QUEUED;
            responseStatusMessage = ErrorMessageBuilder
                    .buildErrorResponse("Error occurred when attempting to send JMS message.", e);
            responseError = ErrorMessageBuilder.buildErrorMessage(connectorProperties.getName(),
                    "Error occurred when attempting to send JMS message.", e);
        } finally {
            eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(),
                    getDestinationName(), ConnectionStatusEventType.IDLE));
        }

        return new Response(responseStatus, null, responseStatusMessage, responseError);
    }

    /**
     * Create a connection key based off the dispatcher properties which can be used to store and
     * identify a connection.
     */
    private String getConnectionKey(JmsDispatcherProperties jmsDispatcherProperties) {
        char delimiter = ':';
        StringBuilder builder = new StringBuilder();

        builder.append(jmsDispatcherProperties.isUseJndi());

        if (jmsDispatcherProperties.isUseJndi()) {
            builder.append(delimiter);
            builder.append(jmsDispatcherProperties.getJndiProviderUrl());

            builder.append(delimiter);
            builder.append(jmsDispatcherProperties.getJndiInitialContextFactory());

            builder.append(delimiter);
            builder.append(jmsDispatcherProperties.getJndiConnectionFactoryName());
        } else {

            builder.append(delimiter);
            builder.append(jmsDispatcherProperties.getConnectionFactoryClass());

            builder.append(delimiter);
            builder.append(jmsDispatcherProperties.isTopic());
        }

        for (String value : jmsDispatcherProperties.getConnectionProperties().values()) {
            builder.append(value);
        }

        builder.append(delimiter);
        builder.append(jmsDispatcherProperties.getUsername());

        builder.append(delimiter);
        builder.append(jmsDispatcherProperties.getPassword());

        builder.append(delimiter);
        builder.append(jmsDispatcherProperties.getClientId());

        return builder.toString();
    }

    /**
     * Get the JmsConnection from the cache if one exists, otherwise a new one will be created. This
     * method is synchronized otherwise multiple threads may try to create the same connection
     * simultaneously. Only one thread is allowed to create a connection at a time. Subsequent
     * threads will then retrieve the connection that was already created.
     */
    private synchronized JmsConnection getJmsConnection(JmsDispatcherProperties jmsDispatcherProperties,
            String connectionKey, Long dispatcherId, boolean replace) throws Exception {
        // If the connection needs to be replaced, clean up the old connection and remove it from the cache.
        if (replace) {
            closeJmsConnectionQuietly(connectionKey);
        }

        JmsConnection jmsConnection = jmsConnections.get(connectionKey);

        if (jmsConnection == null) {
            if (jmsConnections.size() >= maxConnections) {
                throw new Exception("Cannot create new connection. Maximum number (" + maxConnections
                        + ") of cached connections reached.");
            }

            Context initialContext = null;
            ConnectionFactory connectionFactory = null;
            Connection connection = null;

            Map<String, String> connectionProperties = jmsDispatcherProperties.getConnectionProperties();
            if (jmsDispatcherProperties.isUseJndi()) {
                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

                try {
                    MirthContextFactory contextFactory = contextFactoryController
                            .getContextFactory(getResourceIds());
                    Thread.currentThread().setContextClassLoader(contextFactory.getApplicationClassLoader());

                    Hashtable<String, Object> env = new Hashtable<String, Object>();
                    env.put(Context.PROVIDER_URL, jmsDispatcherProperties.getJndiProviderUrl());
                    env.put(Context.INITIAL_CONTEXT_FACTORY,
                            jmsDispatcherProperties.getJndiInitialContextFactory());
                    env.put(Context.SECURITY_PRINCIPAL, jmsDispatcherProperties.getUsername());
                    env.put(Context.SECURITY_CREDENTIALS, jmsDispatcherProperties.getPassword());

                    initialContext = new InitialContext(env);

                    String connectionFactoryName = jmsDispatcherProperties.getJndiConnectionFactoryName();
                    connectionFactory = (ConnectionFactory) initialContext.lookup(connectionFactoryName);
                } finally {
                    Thread.currentThread().setContextClassLoader(contextClassLoader);
                }
            } else {
                String className = jmsDispatcherProperties.getConnectionFactoryClass();

                MirthContextFactory contextFactory = contextFactoryController.getContextFactory(getResourceIds());
                connectionFactory = (ConnectionFactory) Class
                        .forName(className, true, contextFactory.getApplicationClassLoader()).newInstance();
            }

            BeanUtil.setProperties(connectionFactory, connectionProperties);

            try {
                logger.debug("Creating JMS connection and session");
                connection = connectionFactory.createConnection(jmsDispatcherProperties.getUsername(),
                        jmsDispatcherProperties.getPassword());
                String clientId = jmsDispatcherProperties.getClientId();

                if (!clientId.isEmpty()) {
                    connection.setClientID(clientId);
                }

                logger.debug("Starting JMS connection");
                connection.start();
            } catch (JMSException e) {
                try {
                    if (connection != null) {
                        connection.close();
                    }
                } catch (Exception e1) {
                    logger.debug("Failed to close JMS connection.", e);
                }

                try {
                    if (initialContext != null) {
                        initialContext.close();
                    }
                } catch (Exception e1) {
                    logger.debug("Failed to close initial context.", e);
                }

                throw e;
            }

            // Create the new JmsConnection and add it to the cache.
            jmsConnection = new JmsConnection(connection, initialContext);
            jmsConnections.put(connectionKey, jmsConnection);
        }

        return jmsConnection;
    }

    /**
     * Retrieve the dispatcherId specific JmsSession from the cache. If the JmsSession does not
     * exist, create a new one from the connection.
     */
    private JmsSession getJmsSession(JmsConnection jmsConnection, Long dispatcherId) throws Exception {
        Map<Long, JmsSession> jmsSessions = jmsConnection.getJmsSessions();
        JmsSession jmsSession = jmsSessions.get(dispatcherId);

        if (jmsSession == null) {
            Session session = jmsConnection.getConnection().createSession(false, Session.CLIENT_ACKNOWLEDGE);
            MessageProducer producer = session.createProducer(null);

            jmsSession = new JmsSession(session, producer);
            jmsSessions.put(dispatcherId, jmsSession);
        }

        return jmsSession;
    }

    private void closeJmsConnectionQuietly(String connectionKey) {
        try {
            closeJmsConnection(connectionKey);
        } catch (Exception e) {
            logger.debug("Error closing JMS connection on channel " + getChannelId(), e);
        }
    }

    private void closeJmsConnection(String connectionKey) throws Exception {
        JmsConnection jmsConnection = jmsConnections.get(connectionKey);

        if (jmsConnection != null) {
            try {
                Exception firstException = null;

                Connection connection = jmsConnection.getConnection();
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (JMSException e) {
                        firstException = e;
                        logger.debug("Failed to close the JMS connection", e);
                    }
                }

                Context initialContext = jmsConnection.getInitialContext();
                if (initialContext != null) {
                    try {
                        initialContext.close();
                    } catch (NamingException e) {
                        if (firstException == null) {
                            firstException = e;
                        }
                        logger.debug("Failed to close the initial context", e);
                    }

                    initialContext = null;
                }

                if (firstException != null) {
                    throw firstException;
                }
            } finally {
                jmsConnections.remove(connectionKey);
            }
        }
    }

    public Destination getDestination(JmsDispatcherProperties jmsDispatcherProperties, JmsSession jmsSession,
            Context initialContext) throws Exception {
        try {
            String destinationName = jmsDispatcherProperties.getDestinationName();
            if (destinationName.equals(jmsSession.getDestinationName())) {
                /*
                 * Only lookup/create a new destination object if the destination name has changed
                 * since the last time this method was called.
                 */
                return jmsSession.getDestination();
            }

            jmsSession.setDestinationName(destinationName);

            if (jmsDispatcherProperties.isUseJndi()) {
                synchronized (initialContext) {
                    jmsSession.setDestination((Destination) initialContext.lookup(destinationName));
                }
            } else if (jmsDispatcherProperties.isTopic()) {
                jmsSession.setDestination(jmsSession.getSession().createTopic(destinationName));
                logger.debug("Connected to topic: " + destinationName);
            } else {
                jmsSession.setDestination(jmsSession.getSession().createQueue(destinationName));
                logger.debug("Connected to queue: " + destinationName);
            }

            return jmsSession.getDestination();
        } catch (Exception e) {
            jmsSession.setDestination(null);
            jmsSession.setDestinationName(null);

            throw e;
        }
    }

    private class JmsConnection {
        private Connection connection;
        private Context initialContext;
        private Map<Long, JmsSession> jmsSessions = new ConcurrentHashMap<Long, JmsSession>();

        public JmsConnection(Connection connection, Context initialContext) {
            this.connection = connection;
            this.initialContext = initialContext;
        }

        public Connection getConnection() {
            return connection;
        }

        public Context getInitialContext() {
            return initialContext;
        }

        public Map<Long, JmsSession> getJmsSessions() {
            return jmsSessions;
        }
    }

    private class JmsSession {
        private Session session;
        private MessageProducer producer;
        private String destinationName;
        private Destination destination;

        public JmsSession(Session session, MessageProducer producer) {
            this.session = session;
            this.producer = producer;
        }

        public Session getSession() {
            return session;
        }

        public MessageProducer getProducer() {
            return producer;
        }

        public String getDestinationName() {
            return destinationName;
        }

        public void setDestinationName(String destinationName) {
            this.destinationName = destinationName;
        }

        public Destination getDestination() {
            return destination;
        }

        public void setDestination(Destination destination) {
            this.destination = destination;
        }
    }
}