/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.transport.jms.activemq.internal; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.ExceptionListener; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageProducer; import javax.jms.Session; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerPlugin; import org.apache.activemq.broker.BrokerService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.channel.ServerContactPoint; import de.rcenvironment.core.communication.model.NetworkContactPoint; import de.rcenvironment.core.communication.transport.jms.common.InitialInboxConsumer; import de.rcenvironment.core.communication.transport.jms.common.JmsBroker; import de.rcenvironment.core.communication.transport.jms.common.JmsProtocolConstants; import de.rcenvironment.core.communication.transport.jms.common.JmsProtocolUtils; import de.rcenvironment.core.communication.transport.jms.common.RemoteInitiatedMessageChannelFactory; import de.rcenvironment.core.communication.transport.jms.common.RequestInboxConsumer; import de.rcenvironment.core.communication.transport.spi.MessageChannelEndpointHandler; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService; /** * ActiveMQ implementation of the common {@link JmsBroker} interface. It provides an embedded JMS broker for a given * {@link ServerContactPoint} that accepts incoming connections and creates matching remote-initiated ("passive") connections for them. * * @author Robert Mischke */ public class ActiveMQBroker implements JmsBroker { private static final int SHUTDOWN_WAIT_AFTER_ANNOUNCE_MSEC = 1000; // this setting produces warnings if available disk space is less, so replace the default of 50gb - misc_ro // note: this value should have no effect as long as all JMS messages are non-persistent - misc_ro private static final long ACTIVEMQ_TEMPORARY_STORE_LIMIT = 50 * 1024 * 1024; // 50 mb (arbitrary) private static final AtomicInteger sharedInboxConsumerIdGenerator = new AtomicInteger(); private final String brokerName; private final String externalUrl; private final String jvmLocalUrl; private BrokerService brokerService; private Connection localBrokerConnection; private final ServerContactPoint scp; private final RemoteInitiatedMessageChannelFactory remoteInitiatedConnectionFactory; private int numRequestConsumers; private ActiveMQConnectionFilterPlugin connectionFilterPlugin; private final AsyncTaskService threadPool = ConcurrencyUtils.getAsyncTaskService(); private final Log log = LogFactory.getLog(getClass()); public ActiveMQBroker(ServerContactPoint scp, RemoteInitiatedMessageChannelFactory remoteInitiatedConnectionFactory) { this.scp = scp; this.remoteInitiatedConnectionFactory = remoteInitiatedConnectionFactory; NetworkContactPoint ncp = scp.getNetworkContactPoint(); int port = ncp.getPort(); String host = ncp.getHost(); this.brokerName = "RCE_ActiveMQ_" + host + "_" + port; this.externalUrl = "tcp://" + host + ":" + port; this.jvmLocalUrl = "vm://" + brokerName; this.numRequestConsumers = 1; String property = System.getProperty("jms.numRequestConsumers"); if (property != null) { try { numRequestConsumers = Integer.parseInt(property); } catch (NumberFormatException e) { log.warn("Ignoring invalid property value: " + property); } } } @Override public void start() throws Exception { connectionFilterPlugin = new ActiveMQConnectionFilterPlugin(); connectionFilterPlugin.setFilter(scp.getConnectionFilter()); brokerService = createTransientEmbeddedBroker(brokerName, connectionFilterPlugin, externalUrl, jvmLocalUrl); brokerService.start(); ConnectionFactory localConnectionFactory = new ActiveMQConnectionFactory(jvmLocalUrl); localBrokerConnection = localConnectionFactory.createConnection(); localBrokerConnection.setExceptionListener(new ExceptionListener() { @Override public void onException(JMSException exception) { handleAsyncJMSException(exception); } }); localBrokerConnection.start(); spawnInboxConsumers(getLocalConnection()); } @Override public void stop() { try { Session shutdownSession = localBrokerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); try { log.debug("Sending internal queue shutdown commands"); String securityToken = "secToken"; // FIXME use proper token Message poisonPill = JmsProtocolUtils.createQueueShutdownMessage(shutdownSession, securityToken); MessageProducer producer = shutdownSession.createProducer(null); // disposed in finally block (as part of the session) JmsProtocolUtils.configureMessageProducer(producer); producer.send(shutdownSession.createQueue(JmsProtocolConstants.QUEUE_NAME_INITIAL_BROKER_INBOX), poisonPill); for (int i = 0; i < numRequestConsumers; i++) { producer.send(shutdownSession.createQueue(JmsProtocolConstants.QUEUE_NAME_C2B_REQUEST_INBOX), poisonPill); } } finally { shutdownSession.close(); } Thread.sleep(SHUTDOWN_WAIT_AFTER_ANNOUNCE_MSEC); } catch (JMSException e) { log.error("Error while shutting down queue listeners", e); } catch (InterruptedException e) { log.error("Interrupted while waiting for queue shutdown", e); } finally { try { localBrokerConnection.close(); } catch (JMSException e) { log.warn("Error closing local connection to broker " + brokerService.getBrokerName(), e); } // CHECKSTYLE:DISABLE (IllegalCatch) - ActiveMQ method declares "throws Exception" try { brokerService.stop(); log.info("Stopped JMS broker " + brokerService.getBrokerName()); } catch (Exception e) { log.warn("Error shutting down JMS broker " + brokerService.getBrokerName(), e); } // CHECKSTYLE:ENABLE (IllegalCatch) } } @Override public Connection getLocalConnection() { return localBrokerConnection; } private static BrokerService createTransientEmbeddedBroker(String brokerName, ActiveMQConnectionFilterPlugin filterPlugin, final String... urls) throws Exception { final BrokerService broker = new BrokerService(); broker.setBrokerName(brokerName); broker.setPersistent(false); broker.setUseJmx(false); // default=true broker.getSystemUsage().getTempUsage().setLimit(ACTIVEMQ_TEMPORARY_STORE_LIMIT); // TODO ActiveMQ broker properties to set/evaluate: // - schedulePeriodForDestinationPurge // - inactiveTimoutBeforeGC // - timeBeforePurgeTempDestinations // ... broker.setPlugins(new BrokerPlugin[] { filterPlugin }); // note: must be set before connectors are added for (String url : urls) { broker.addConnector(url); } return broker; } private void spawnInboxConsumers(Connection connection) throws JMSException { log.debug("Spawning initial inbox consumer for " + scp.toString()); threadPool.execute(new InitialInboxConsumer(connection, scp, remoteInitiatedConnectionFactory)); log.debug("Spawning " + numRequestConsumers + " request inbox consumer(s) for " + scp); final MessageChannelEndpointHandler endpointHandler = scp.getEndpointHandler(); for (int i = 1; i <= numRequestConsumers; i++) { threadPool.execute( new RequestInboxConsumer(JmsProtocolConstants.QUEUE_NAME_C2B_REQUEST_INBOX, connection, endpointHandler), StringUtils.format("Shared C2B Request Inbox Consumer #%d (worker #%d for %s')", sharedInboxConsumerIdGenerator.incrementAndGet(), i, scp.toString())); } } private void handleAsyncJMSException(JMSException e) { final String exceptionString = e.toString(); final boolean isKnownHarmlessMessage = exceptionString.contains("The destination temp-queue") || exceptionString.contains("Cannot remove session that had not been registered"); if (isKnownHarmlessMessage) { // do not log full stack trace as it contains no useful information log.debug("Asynchronous JMS exception (usually a follow-up error of a broken connection): " + exceptionString); } else { log.warn("Asynchronous JMS exception in local broker connection", e); } } }