Java tutorial
/* * 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.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.ExceptionListener; import javax.jms.JMSException; import javax.jms.Session; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import org.apache.commons.lang3.math.NumberUtils; import org.apache.log4j.Logger; import com.mirth.connect.donkey.model.channel.DeployedState; import com.mirth.connect.donkey.model.channel.DestinationConnectorPropertiesInterface; import com.mirth.connect.donkey.model.channel.SourceConnectorPropertiesInterface; import com.mirth.connect.donkey.model.event.ConnectionStatusEventType; import com.mirth.connect.donkey.model.event.ErrorEventType; import com.mirth.connect.donkey.server.ConnectorTaskException; import com.mirth.connect.donkey.server.StopException; import com.mirth.connect.donkey.server.channel.Connector; 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; /** * Represents the client connection to a JMS broker, used by both the JMS receiver and dispatcher * connectors */ public class JmsClient implements ExceptionListener { private Connector connector; private JmsReceiverProperties connectorProperties; private String connectorName; private Connection connection; private Session session; private Context initialContext; private Destination destination; private String destinationName; private EventController eventController = ControllerFactory.getFactory().createEventController(); private TemplateValueReplacer replacer = new TemplateValueReplacer(); private Thread reconnectThread; private AtomicBoolean connected = new AtomicBoolean(false); private AtomicBoolean attemptingReconnect = new AtomicBoolean(false); private int intervalMillis; private Logger logger = Logger.getLogger(getClass()); private ContextFactoryController contextFactoryController = ControllerFactory.getFactory() .createContextFactoryController(); private Set<String> resourceIds; public JmsClient(final Connector connector, JmsReceiverProperties connectorProperties, String connectorName) { this.connector = connector; this.connectorProperties = connectorProperties; this.connectorName = connectorName; this.intervalMillis = NumberUtils .toInt(replacer.replaceValues(connectorProperties.getReconnectIntervalMillis(), connector.getChannelId(), connector.getChannel().getName())); if (connectorProperties instanceof SourceConnectorPropertiesInterface) { resourceIds = ((SourceConnectorPropertiesInterface) connectorProperties).getSourceConnectorProperties() .getResourceIds().keySet(); } else if (connectorProperties instanceof DestinationConnectorPropertiesInterface) { resourceIds = ((DestinationConnectorPropertiesInterface) connectorProperties) .getDestinationConnectorProperties().getResourceIds().keySet(); } } private ConnectionFactory lookupConnectionFactoryWithJndi() throws Exception { String channelId = connector.getChannelId(); String channelName = connector.getChannel().getName(); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { MirthContextFactory contextFactory = contextFactoryController.getContextFactory(resourceIds); Thread.currentThread().setContextClassLoader(contextFactory.getApplicationClassLoader()); Hashtable<String, Object> env = new Hashtable<String, Object>(); env.put(Context.PROVIDER_URL, replacer.replaceValues(connectorProperties.getJndiProviderUrl(), channelId, channelName)); env.put(Context.INITIAL_CONTEXT_FACTORY, replacer .replaceValues(connectorProperties.getJndiInitialContextFactory(), channelId, channelName)); env.put(Context.SECURITY_PRINCIPAL, replacer.replaceValues(connectorProperties.getUsername(), channelId, channelName)); env.put(Context.SECURITY_CREDENTIALS, replacer.replaceValues(connectorProperties.getPassword(), channelId, channelName)); initialContext = new InitialContext(env); String connectionFactoryName = replacer .replaceValues(connectorProperties.getJndiConnectionFactoryName(), channelId, channelName); return (ConnectionFactory) initialContext.lookup(connectionFactoryName); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } /** * Starts a JMS connection and session. */ public void start() throws ConnectorTaskException { final String channelId = connector.getChannelId(); String channelName = connector.getChannel().getName(); Map<String, String> connectionProperties = replacer .replaceValues(connectorProperties.getConnectionProperties(), channelId, channelName); ConnectionFactory connectionFactory = null; if (connectorProperties.isUseJndi()) { try { connectionFactory = lookupConnectionFactoryWithJndi(); } catch (Exception e) { throw new ConnectorTaskException("Failed to obtain the connection factory via JNDI", e); } } else { String className = replacer.replaceValues(connectorProperties.getConnectionFactoryClass(), channelId, channelName); try { MirthContextFactory contextFactory = contextFactoryController.getContextFactory(resourceIds); connectionFactory = (ConnectionFactory) Class .forName(className, true, contextFactory.getApplicationClassLoader()).newInstance(); } catch (Exception e) { throw new ConnectorTaskException("Failed to instantiate ConnectionFactory class: " + className, e); } } BeanUtil.setProperties(connectionFactory, connectionProperties); try { logger.debug("Creating JMS connection and session"); connection = connectionFactory.createConnection( replacer.replaceValues(connectorProperties.getUsername(), channelId, channelName), replacer.replaceValues(connectorProperties.getPassword(), channelId, channelName)); String clientId = replacer.replaceValues(connectorProperties.getClientId(), channelId, channelName); if (!clientId.isEmpty()) { connection.setClientID(clientId); } connection.setExceptionListener(this); logger.debug("Starting JMS connection"); connection.start(); logger.debug("Creating JMS session"); session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); logger.debug("JMS session created"); } catch (JMSException e) { try { stop(); } catch (Exception e1) { } throw new ConnectorTaskException("Failed to establish a JMS connection", e); } connected.set(true); } /** * Closes/stops the JMS connection. */ public void stop() throws StopException { if (connection != null) { try { connection.close(); } catch (JMSException e) { throw new StopException("Failed to close the JMS connection", e); } } if (initialContext != null) { try { initialContext.close(); } catch (NamingException e) { logger.error("Failed to close the initial context", e); } initialContext = null; } connected.set(false); } public Connection getConnection() { return connection; } public Session getSession() { return session; } /* * This method is synchronized in case a queued destination is running in "attempt first" mode. * The queue thread and the destination's "attempt first" thread could potentially execute this * method at the same time. */ public synchronized Destination getDestination(String destinationName) throws JMSException, NamingException { /* * Only lookup/create a new destination object if the destination name has changed since the * last time this method was called. */ if (destinationName.equals(this.destinationName)) { return destination; } this.destinationName = destinationName; if (connectorProperties.isUseJndi()) { destination = (Destination) initialContext.lookup(destinationName); } else if (connectorProperties.isTopic()) { destination = session.createTopic(destinationName); logger.debug("Connected to topic: " + destinationName); } else { destination = session.createQueue(destinationName); logger.debug("Connected to queue: " + destinationName); } return destination; } /* * Whenever an exception occurs, we want to try to reconnect to the JMS broker. */ @Override public void onException(JMSException e) { /* * If reconnecting is true, then we return because we don't want to create an infinite loop * if the reconnect attempt itself throws an exception. */ if (attemptingReconnect.get()) { return; } beginReconnect(false); } /** * Begin reconnect attempts if we've detected that the connect to the JMS broker is down. * * @param force * If true, forces a reconnect attempt right now and returns the result. * @return The result of the force reconnect attempt. */ public synchronized boolean beginReconnect(boolean force) { if (connected.get()) { eventController.dispatchEvent(new ConnectionStatusEvent(connector.getChannelId(), connector.getMetaDataId(), connectorName, ConnectionStatusEventType.DISCONNECTED)); connected.set(false); } if (intervalMillis == 0) { doReconnect(); return connected.get(); } else { if (reconnectThread == null || !reconnectThread.isAlive()) { reconnectThread = new ReconnectThread(); reconnectThread.start(); } if (force) { reconnectThread.interrupt(); try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } return connected.get(); } else { return false; } } } private class ReconnectThread extends Thread { @Override public void run() { while (!connected.get() && connector.getCurrentState() == DeployedState.STARTED) { try { if (!connected.get()) { doReconnect(); } Thread.sleep(intervalMillis); } catch (InterruptedException e) { } } } } private synchronized void doReconnect() { try { attemptingReconnect.set(true); logger.debug("attempting to reconnect"); try { stop(); } catch (StopException e1) { } connector.onStart(); logger.debug("reconnect successful"); } catch (ConnectorTaskException e) { reportError("Failed to reconnect", e); } finally { attemptingReconnect.set(false); /* * Notify beginReconnect() that we just attempted to reconnect */ notify(); } } private void reportError(String errorMessage, Exception e) { String channelId = connector.getChannelId(); logger.error(errorMessage + " (channel: " + ChannelController.getInstance().getDeployedChannelById(channelId).getName() + ")", e); eventController.dispatchEvent( new ErrorEvent(channelId, connector.getMetaDataId(), null, ErrorEventType.DESTINATION_CONNECTOR, connectorName, connectorProperties.getName(), null, e.getCause())); } }