Java tutorial
/* * Milyn - Copyright (C) 2006 - 2010 * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License (version 2.1) as published * by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * * See the GNU Lesser General Public License for more details: * http://www.gnu.org/licenses/lgpl.txt */ package org.dhatim.routing.jms; import java.io.IOException; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.QueueBrowser; import javax.jms.QueueSession; import javax.jms.Session; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dhatim.SmooksException; import org.dhatim.assertion.AssertArgument; import org.dhatim.cdr.SmooksConfigurationException; import org.dhatim.cdr.annotation.ConfigParam; import org.dhatim.cdr.annotation.ConfigParam.Use; import org.dhatim.container.ExecutionContext; import org.dhatim.delivery.annotation.Initialize; import org.dhatim.delivery.annotation.Uninitialize; import org.dhatim.delivery.annotation.VisitAfterIf; import org.dhatim.delivery.annotation.VisitBeforeIf; import org.dhatim.delivery.dom.DOMElementVisitor; import org.dhatim.delivery.sax.*; import org.dhatim.delivery.ordering.Consumer; import org.dhatim.routing.SmooksRoutingException; import org.dhatim.routing.jms.message.creationstrategies.MessageCreationStrategy; import org.dhatim.routing.jms.message.creationstrategies.StrategyFactory; import org.dhatim.routing.jms.message.creationstrategies.TextMessageCreationStrategy; import org.dhatim.util.FreeMarkerUtils; import org.dhatim.util.FreeMarkerTemplate; import org.w3c.dom.Element; /** * <p/> * Router is a Visitor for DOM or SAX elements. It sends the content * as a JMS Message object to the configured destination. * <p/> * The type of the JMS Message is determined by the "messageType" config param. * <p/> * Example configuration: * <pre> * <resource-config selector="orderItems"> * <resource>org.dhatim.routing.jms.JMSRouter</resource> * <param name="beanId">beanId</param> * <param name="destinationName">/queue/smooksRouterQueue</param> * </resource-config> * .... * Optional parameters: * <param name="executeBefore">true</param> * <param name="jndiContextFactory">ConnectionFactory</param> * <param name="jndiProviderUrl">jnp://localhost:1099</param> * <param name="jndiNamingFactory">org.jboss.naming:java.naming.factory.url.pkgs=org.jnp.interfaces</param> * <param name="connectionFactory">ConnectionFactory</param> * <param name="deliveryMode">persistent</param> * <param name="priority">10</param> * <param name="timeToLive">100000</param> * <param name="securityPrincipal">username</param> * <param name="securityCredential">password</param> * <param name="acknowledgeMode">AUTO_ACKNOWLEDGE</param> * <param name="transacted">false</param> * <param name="correlationIdPattern">orderitem-${order.orderId}-${order.orderItem.itemId}</param> * <param name="messageType">ObjectMessage</param> * <param name="highWaterMark">50</param> * <param name="highWaterMarkTimeout">5000</param> * <param name="highWaterMarkPollFrequency">500</param> * </pre> * Description of configuration properties: * <ul> * <li><i>jndiContextFactory</i>: the JNDI ContextFactory to use. * <li><i>jndiProviderUrl</i>: the JNDI Provider URL to use. * <li><i>jndiNamingFactory</i>: the JNDI NamingFactory to use. * <li><i>connectionFactory</i>: the ConnectionFactory to look up. * <li><i>deliveryMode</i>: the JMS DeliveryMode. 'persistent'(default) or 'non-persistent'. * <li><i>priority</i>: the JMS Priority to be used. * <li><i>timeToLive</i>: the JMS Time-To-Live to be used. * <li><i>securityPrincipal</i>: security principal use when creating the JMS connection. * <li><i>securityCredential</i>: the security credentials to use when creating the JMS connection. * <li><i>acknowledgeMode</i>: the acknowledge mode to use. One of 'AUTO_ACKNOWLEDGE'(default), 'CLIENT_ACKNOWLEDGE', 'DUPS_OK_ACKNOWLEDGE'. * <li><i>transacted</i>: determines if the session should be transacted. Defaults to 'false'. * <li><i>correlationIdPattern</i>: JMS Correlation pattern that will be used for the outgoing message. Supports templating. * <li><i>messageType</i>: type of JMS Message that should be sent. 'TextMessage'(default), 'ObjectMessage' or 'MapMessage'. * <li><i>highWaterMark</i>: max number of messages that can be sitting in the JMS Destination at any any time. Default is 200. * <li><i>highWaterMarkTimeout</i>: number of ms to wait for the system to process JMS Messages from the JMS destination * so that the number of JMS Messages drops below the highWaterMark. Default is 60000 ms. * <li><i>highWaterMarkPollFrequency</i>: number of ms to wait between checks on the High Water Mark, while * waiting for it to drop. Default is 1000 ms. * </ul> * * @author <a href="mailto:daniel.bevenius@gmail.com">Daniel Bevenius</a> * @since 1.0 * */ @VisitBeforeIf(condition = "parameters.containsKey('executeBefore') && parameters.executeBefore.value == 'true'") @VisitAfterIf(condition = "!parameters.containsKey('executeBefore') || parameters.executeBefore.value != 'true'") public class JMSRouter implements DOMElementVisitor, SAXVisitBefore, SAXVisitAfter, Consumer { /* * Log instance */ private final Log logger = LogFactory.getLog(JMSRouter.class); /* * JNDI Properties holder */ private final JNDIProperties jndiProperties = new JNDIProperties(); /* * JMS Properties holder */ private final JMSProperties jmsProperties = new JMSProperties(); /* * BeanId is a key that is used to look up a bean * in the execution context */ @ConfigParam(use = ConfigParam.Use.REQUIRED) private String beanId; @ConfigParam(use = ConfigParam.Use.OPTIONAL) private String correlationIdPattern; private FreeMarkerTemplate correlationIdTemplate; @ConfigParam(defaultVal = "200") private int highWaterMark = 200; @ConfigParam(defaultVal = "60000") private long highWaterMarkTimeout = 60000; @ConfigParam(defaultVal = "1000") private long highWaterMarkPollFrequency = 1000; /* * Strategy for JMS Message object creation */ private MessageCreationStrategy msgCreationStrategy = new TextMessageCreationStrategy(); /* * JMS Destination */ private Destination destination; /* * JMS Connection */ private Connection connection; /* * JMS Message producer */ private MessageProducer msgProducer; /* * JMS Session */ private Session session; @Initialize public void initialize() throws SmooksConfigurationException, JMSException { Context context = null; boolean initialized = false; if (beanId == null) { throw new SmooksConfigurationException("Mandatory 'beanId' property not defined."); } if (jmsProperties.getDestinationName() == null) { throw new SmooksConfigurationException("Mandatory 'destinationName' property not defined."); } try { if (correlationIdPattern != null) { correlationIdTemplate = new FreeMarkerTemplate(correlationIdPattern); } Properties jndiContextProperties = jndiProperties.toProperties(); if (jndiContextProperties.isEmpty()) { context = new InitialContext(); } else { context = new InitialContext(jndiContextProperties); } destination = (Destination) context.lookup(jmsProperties.getDestinationName()); msgProducer = createMessageProducer(destination, context); setMessageProducerProperties(); initialized = true; } catch (NamingException e) { final String errorMsg = "NamingException while trying to lookup [" + jmsProperties.getDestinationName() + "]"; logger.error(errorMsg, e); throw new SmooksConfigurationException(errorMsg, e); } finally { if (context != null) { try { context.close(); } catch (NamingException e) { logger.debug("NamingException while trying to close initial Context"); } } if (!initialized) { releaseJMSResources(); } } } @Uninitialize public void uninitialize() throws JMSException { releaseJMSResources(); } public boolean consumes(Object object) { if (object.toString().startsWith(beanId)) { // We use startsWith (Vs equals) so as to catch bean populations e.g. "address.street". return true; } return false; } public void setBeanId(String beanId) { AssertArgument.isNotNullAndNotEmpty(beanId, "beanId"); this.beanId = beanId; } public void setCorrelationIdPattern(String correlationIdPattern) { this.correlationIdPattern = correlationIdPattern; } public void setHighWaterMark(int highWaterMark) { this.highWaterMark = highWaterMark; } public void setHighWaterMarkTimeout(long highWaterMarkTimeout) { this.highWaterMarkTimeout = highWaterMarkTimeout; } public void setHighWaterMarkPollFrequency(long highWaterMarkPollFrequency) { this.highWaterMarkPollFrequency = highWaterMarkPollFrequency; } @ConfigParam(use = Use.OPTIONAL) public void setJndiContextFactory(final String contextFactory) { jndiProperties.setContextFactory(contextFactory); } @ConfigParam(use = Use.OPTIONAL) public void setJndiProperties(final String propertiesFile) { jndiProperties.setPropertiesFile(propertiesFile); } public void setJndiProperties(final Properties properties) { jndiProperties.setProperties(properties); } @ConfigParam(use = Use.OPTIONAL) public void setJndiProviderUrl(final String providerUrl) { jndiProperties.setProviderUrl(providerUrl); } @ConfigParam(use = Use.OPTIONAL) public void setJndiNamingFactoryUrl(final String pkgUrl) { jndiProperties.setNamingFactoryUrlPkgs(pkgUrl); } @ConfigParam(use = Use.REQUIRED) public void setDestinationName(final String destinationName) { AssertArgument.isNotNullAndNotEmpty(destinationName, "destinationName"); jmsProperties.setDestinationName(destinationName); } @ConfigParam(choice = { "persistent", "non-persistent" }, defaultVal = "persistent", use = Use.OPTIONAL) public void setDeliveryMode(final String deliveryMode) { jmsProperties.setDeliveryMode(deliveryMode); } @ConfigParam(use = Use.OPTIONAL) public void setTimeToLive(final long timeToLive) { jmsProperties.setTimeToLive(timeToLive); } @ConfigParam(use = Use.OPTIONAL) public void setSecurityPrincipal(final String securityPrincipal) { jmsProperties.setSecurityPrincipal(securityPrincipal); } @ConfigParam(use = Use.OPTIONAL) public void setSecurityCredential(final String securityCredential) { jmsProperties.setSecurityCredential(securityCredential); } @ConfigParam(use = Use.OPTIONAL, defaultVal = "false") public void setTransacted(final boolean transacted) { jmsProperties.setTransacted(transacted); } @ConfigParam(defaultVal = "ConnectionFactory", use = Use.OPTIONAL) public void setConnectionFactoryName(final String connectionFactoryName) { jmsProperties.setConnectionFactoryName(connectionFactoryName); } @ConfigParam(use = Use.OPTIONAL) public void setPriority(final int priority) { jmsProperties.setPriority(priority); } @ConfigParam(defaultVal = "AUTO_ACKNOWLEDGE", choice = { "AUTO_ACKNOWLEDGE", "CLIENT_ACKNOWLEDGE", "DUPS_OK_ACKNOWLEDGE" }) public void setAcknowledgeMode(final String jmsAcknowledgeMode) { jmsProperties.setAcknowledgeMode(jmsAcknowledgeMode); } @ConfigParam(defaultVal = StrategyFactory.TEXT_MESSAGE, choice = { StrategyFactory.TEXT_MESSAGE, StrategyFactory.OBJECT_MESSAGE }) public void setMessageType(final String messageType) { msgCreationStrategy = StrategyFactory.getInstance().createStrategy(messageType); jmsProperties.setMessageType(messageType); } // Vistor methods public void visitAfter(final Element element, final ExecutionContext execContext) throws SmooksException { visit(execContext); } public void visitBefore(final Element element, final ExecutionContext execContext) throws SmooksException { visit(execContext); } public void visitAfter(final SAXElement element, final ExecutionContext execContext) throws SmooksException, IOException { visit(execContext); } public void visitBefore(final SAXElement element, final ExecutionContext execContext) throws SmooksException, IOException { visit(execContext); } private void visit(final ExecutionContext execContext) throws SmooksException { Message message = msgCreationStrategy.createJMSMessage(beanId, execContext, session); if (correlationIdTemplate != null) { setCorrelationID(execContext, message); } sendMessage(message); } // Lifecycle protected MessageProducer createMessageProducer(final Destination destination, final Context context) throws JMSException { try { final ConnectionFactory connFactory = (ConnectionFactory) context .lookup(jmsProperties.getConnectionFactoryName()); connection = (jmsProperties.getSecurityPrincipal() == null && jmsProperties.getSecurityCredential() == null) ? connFactory.createConnection() : connFactory.createConnection(jmsProperties.getSecurityPrincipal(), jmsProperties.getSecurityCredential()); session = connection.createSession(jmsProperties.isTransacted(), AcknowledgeModeEnum .getAckMode(jmsProperties.getAcknowledgeMode().toUpperCase()).getAcknowledgeModeInt()); msgProducer = session.createProducer(destination); connection.start(); logger.info("JMS Connection started"); } catch (JMSException e) { final String errorMsg = "JMSException while trying to create MessageProducer for Queue [" + jmsProperties.getDestinationName() + "]"; releaseJMSResources(); throw new SmooksConfigurationException(errorMsg, e); } catch (NamingException e) { final String errorMsg = "NamingException while trying to lookup ConnectionFactory [" + jmsProperties.getConnectionFactoryName() + "]"; releaseJMSResources(); throw new SmooksConfigurationException(errorMsg, e); } return msgProducer; } /** * Sets the following MessageProducer properties: * <lu> * <li>TimeToLive * <li>Priority * <li>DeliveryMode * </lu> * <p> * Subclasses may override this behaviour. */ protected void setMessageProducerProperties() throws SmooksConfigurationException { try { msgProducer.setTimeToLive(jmsProperties.getTimeToLive()); msgProducer.setPriority(jmsProperties.getPriority()); final int deliveryModeInt = "non-persistent".equals(jmsProperties.getDeliveryMode()) ? DeliveryMode.NON_PERSISTENT : DeliveryMode.PERSISTENT; msgProducer.setDeliveryMode(deliveryModeInt); } catch (JMSException e) { final String errorMsg = "JMSException while trying to set JMS Header Fields"; throw new SmooksConfigurationException(errorMsg, e); } } protected void sendMessage(final Message message) throws SmooksRoutingException { try { waitWhileAboveHighWaterMark(); } catch (JMSException e) { throw new SmooksRoutingException("Exception while attempting to check JMS Queue High Water Mark.", e); } try { msgProducer.send(message); } catch (JMSException e) { final String errorMsg = "JMSException while sending Message."; throw new SmooksRoutingException(errorMsg, e); } } private void waitWhileAboveHighWaterMark() throws JMSException, SmooksRoutingException { if (highWaterMark == -1) { return; } if (session instanceof QueueSession) { QueueSession queueSession = (QueueSession) session; QueueBrowser queueBrowser = queueSession.createBrowser((Queue) destination); try { int length = getQueueLength(queueBrowser); long start = System.currentTimeMillis(); if (logger.isDebugEnabled() && length >= highWaterMark) { logger.debug("Length of JMS destination Queue '" + jmsProperties.getDestinationName() + "' has reached " + length + ". High Water Mark is " + highWaterMark + ". Waiting for Queue length to drop."); } while (length >= highWaterMark && (System.currentTimeMillis() < start + highWaterMarkTimeout)) { try { Thread.sleep(highWaterMarkPollFrequency); } catch (InterruptedException e) { logger.error("Interrupted", e); return; } length = getQueueLength(queueBrowser); } // Check did the queue length drop below the HWM... if (length >= highWaterMark) { throw new SmooksRoutingException("Failed to route JMS message to Queue destination '" + ((Queue) destination).getQueueName() + "'. Timed out (" + highWaterMarkTimeout + " ms) waiting for queue length to drop below High Water Mark (" + highWaterMark + "). Consider increasing 'highWaterMark' and/or 'highWaterMarkTimeout' param values."); } } finally { queueBrowser.close(); } } } private int getQueueLength(QueueBrowser queueBrowser) throws JMSException { int length = 0; Enumeration queueEnum = queueBrowser.getEnumeration(); while (queueEnum.hasMoreElements()) { length++; queueEnum.nextElement(); } return length; } protected void close(final Connection connection) { if (connection != null) { try { connection.close(); } catch (JMSException e) { final String errorMsg = "JMSException while trying to close connection"; logger.debug(errorMsg, e); } } } protected void close(final Session session) { if (session != null) { try { session.close(); } catch (JMSException e) { final String errorMsg = "JMSException while trying to close session"; logger.debug(errorMsg, e); } } } public Destination getDestination() { return destination; } public String getJndiContextFactory() { return jndiProperties.getContextFactory(); } public String getJndiProviderUrl() { return jndiProperties.getProviderUrl(); } public String getJndiNamingFactoryUrl() { return jndiProperties.getNamingFactoryUrlPkgs(); } public String getDestinationName() { return jmsProperties.getDestinationName(); } private void setCorrelationID(ExecutionContext execContext, Message message) { Map<String, Object> beanMap = FreeMarkerUtils.getMergedModel(execContext); String correlationId = correlationIdTemplate.apply(beanMap); try { message.setJMSCorrelationID(correlationId); } catch (JMSException e) { throw new SmooksException("Failed to set CorrelationID '" + correlationId + "' on message.", e); } } public String getDeliveryMode() { return jmsProperties.getDeliveryMode(); } public long getTimeToLive() { return jmsProperties.getTimeToLive(); } public String getSecurityPrincipal() { return jmsProperties.getSecurityPrincipal(); } public String getSecurityCredential() { return jmsProperties.getSecurityCredential(); } public boolean isTransacted() { return jmsProperties.isTransacted(); } public String getConnectionFactoryName() { return jmsProperties.getConnectionFactoryName(); } public int getPriority() { return jmsProperties.getPriority(); } public String getAcknowledgeMode() { return jmsProperties.getAcknowledgeMode(); } public void setMsgCreationStrategy(final MessageCreationStrategy msgCreationStrategy) { this.msgCreationStrategy = msgCreationStrategy; } private void releaseJMSResources() throws JMSException { if (connection != null) { try { try { connection.stop(); } finally { try { closeProducer(); } finally { closeSession(); } } } catch (JMSException e) { logger.debug("JMSException while trying to stop JMS Connection.", e); } finally { connection.close(); connection = null; } } } private void closeProducer() { if (msgProducer != null) { try { msgProducer.close(); } catch (JMSException e) { logger.debug("JMSException while trying to close JMS Message Producer.", e); } finally { msgProducer = null; } } } private void closeSession() { if (session != null) { try { session.close(); } catch (JMSException e) { logger.debug("JMSException while trying to close JMS Session.", e); } finally { session = null; } } } }