Coverage Report - org.mule.transport.jms.JmsMessageDispatcher
 
Classes in this File Line Coverage Branch Coverage Complexity
JmsMessageDispatcher
0%
0/166
0%
0/124
0
JmsMessageDispatcher$ReplyToListener
0%
0/13
N/A
0
 
 1  
 /*
 2  
  * $Id: JmsMessageDispatcher.java 20321 2010-11-24 15:21:24Z dfeist $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 
 11  
 package org.mule.transport.jms;
 12  
 
 13  
 import org.mule.api.MuleEvent;
 14  
 import org.mule.api.MuleException;
 15  
 import org.mule.api.MuleMessage;
 16  
 import org.mule.api.config.MuleProperties;
 17  
 import org.mule.api.endpoint.EndpointBuilder;
 18  
 import org.mule.api.endpoint.EndpointException;
 19  
 import org.mule.api.endpoint.OutboundEndpoint;
 20  
 import org.mule.api.lifecycle.InitialisationException;
 21  
 import org.mule.api.transaction.Transaction;
 22  
 import org.mule.api.transformer.TransformerException;
 23  
 import org.mule.api.transport.DispatchException;
 24  
 import org.mule.config.i18n.CoreMessages;
 25  
 import org.mule.transaction.TransactionCoordination;
 26  
 import org.mule.transport.AbstractMessageDispatcher;
 27  
 import org.mule.transport.jms.i18n.JmsMessages;
 28  
 import org.mule.util.ClassUtils;
 29  
 import org.mule.util.NumberUtils;
 30  
 import org.mule.util.concurrent.Latch;
 31  
 import org.mule.util.concurrent.WaitableBoolean;
 32  
 
 33  
 import javax.jms.DeliveryMode;
 34  
 import javax.jms.Destination;
 35  
 import javax.jms.JMSException;
 36  
 import javax.jms.Message;
 37  
 import javax.jms.MessageConsumer;
 38  
 import javax.jms.MessageListener;
 39  
 import javax.jms.MessageProducer;
 40  
 import javax.jms.Session;
 41  
 import javax.jms.TemporaryQueue;
 42  
 import javax.jms.TemporaryTopic;
 43  
 
 44  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 45  
 
 46  
 /**
 47  
  * <code>JmsMessageDispatcher</code> is responsible for dispatching messages to JMS
 48  
  * destinations. All JMS semantics apply and settings such as replyTo and QoS
 49  
  * properties are read from the event properties or defaults are used (according to
 50  
  * the JMS specification)
 51  
  */
 52  
 public class JmsMessageDispatcher extends AbstractMessageDispatcher
 53  
 {
 54  
 
 55  
     private JmsConnector connector;
 56  
     private Session cachedSession;
 57  0
     private boolean disableTemporaryDestinations = false;
 58  0
     private boolean returnOriginalMessageAsReply = false;
 59  
 
 60  
     public JmsMessageDispatcher(OutboundEndpoint endpoint)
 61  
     {
 62  0
         super(endpoint);
 63  0
         this.connector = (JmsConnector) endpoint.getConnector();
 64  0
         disableTemporaryDestinations = connector.isDisableTemporaryReplyToDestinations() ||
 65  
             ("true".equals(endpoint.getProperty(JmsConstants.DISABLE_TEMP_DESTINATIONS_PROPERTY)));
 66  0
         returnOriginalMessageAsReply = connector.isReturnOriginalMessageAsReply() ||
 67  
             ("true".equals(endpoint.getProperty(JmsConstants.RETURN_ORIGINAL_MESSAGE_PROPERTY)));
 68  0
         if (returnOriginalMessageAsReply && !disableTemporaryDestinations)
 69  
         {
 70  0
             logger.warn("The returnOriginalMessageAsReply property will be ignored because disableTemporaryReplyToDestinations=false.  You need to disable temporary ReplyTo destinations in order for this propery to take effect.");
 71  
         }
 72  0
     }
 73  
 
 74  
     @Override
 75  
     protected void doDispatch(MuleEvent event) throws Exception
 76  
     {
 77  0
         if (connector.getConnection() == null)
 78  
         {
 79  0
             throw new IllegalStateException("No JMS Connection");
 80  
         }
 81  0
         dispatchMessage(event, false);
 82  0
     }
 83  
 
 84  
     @Override
 85  
     protected void doConnect() throws Exception
 86  
     {
 87  
         // template method
 88  0
     }
 89  
 
 90  
     @Override
 91  
     protected void doDisconnect() throws Exception
 92  
     {
 93  
         // template method
 94  0
     }
 95  
 
 96  
     protected boolean isDisableTemporaryDestinations()
 97  
     {
 98  0
         return disableTemporaryDestinations;
 99  
     }
 100  
 
 101  
     private MuleMessage dispatchMessage(MuleEvent event, boolean doSend) throws Exception
 102  
     {
 103  0
         Session session = null;
 104  0
         MessageProducer producer = null;
 105  0
         MessageConsumer consumer = null;
 106  0
         Destination replyTo = null;
 107  0
         boolean transacted = false;
 108  0
         boolean cached = false;
 109  
         boolean useReplyToDestination;
 110  
 
 111  0
         final Transaction muleTx = TransactionCoordination.getInstance().getTransaction();
 112  
 
 113  0
         if (logger.isDebugEnabled())
 114  
         {
 115  0
             logger.debug("dispatching on endpoint: " + event.getEndpoint().getEndpointURI()
 116  
                     + ". MuleEvent id is: " + event.getId()
 117  
                     + ". Outbound transformers are: " + event.getEndpoint().getTransformers());
 118  
         }
 119  
 
 120  
         // assume session is transacted first, and thus, managed
 121  0
         boolean sessionManaged = true;
 122  
         try
 123  
         {
 124  0
             session = connector.getSessionFromTransaction();
 125  0
             if (session != null)
 126  
             {
 127  0
                 transacted = true;
 128  
             }
 129  
             // Should we be caching sessions? Note this is not part of the JMS spec.
 130  
             // and is turned off by default.
 131  0
             else if (event.getMessage().getOutboundProperty(JmsConstants.CACHE_JMS_SESSIONS_PROPERTY, connector.isCacheJmsSessions()))
 132  
             {
 133  0
                 sessionManaged = false;
 134  0
                 cached = true;
 135  0
                 if (cachedSession != null)
 136  
                 {
 137  0
                     session = cachedSession;
 138  
                 }
 139  
                 else
 140  
                 {
 141  0
                     session = connector.getSession(event.getEndpoint());
 142  0
                     cachedSession = session;
 143  
                 }
 144  
             }
 145  
             else
 146  
             {
 147  
                 // by now we're running with a different connector and connection
 148  0
                 sessionManaged = muleTx != null && muleTx.isXA();
 149  
 
 150  0
                 session = connector.getSession(event.getEndpoint());
 151  0
                 if (event.getEndpoint().getTransactionConfig().isTransacted())
 152  
                 {
 153  0
                     transacted = true;
 154  
                 }
 155  
             }
 156  
 
 157  
             // If a transaction is running, we can not receive any messages
 158  
             // in the same transaction using a replyTo destination
 159  0
             useReplyToDestination = returnResponse(event, doSend) && !transacted;
 160  
 
 161  0
             boolean topic = connector.getTopicResolver().isTopic(event.getEndpoint(), true);
 162  
 
 163  0
             Destination dest = connector.getJmsSupport().createDestination(session, endpoint);
 164  0
             producer = connector.getJmsSupport().createProducer(session, dest, topic);
 165  
 
 166  0
             Object message = event.getMessage().getPayload();
 167  0
             if (!(message instanceof Message))
 168  
             {
 169  0
                 throw new DispatchException(
 170  
                         JmsMessages.checkTransformer("JMS message", message.getClass(), connector.getName()),
 171  
                         event, (OutboundEndpoint) endpoint);
 172  
             }
 173  
 
 174  0
             Message msg = (Message) message;
 175  
 
 176  0
             MuleMessage eventMsg = event.getMessage();
 177  
 
 178  0
             replyTo = getReplyToDestination(msg, session, event, useReplyToDestination, topic);
 179  
 
 180  
             // Set the replyTo property
 181  0
             if (replyTo != null)
 182  
             {
 183  0
                 msg.setJMSReplyTo(replyTo);
 184  
             }
 185  
 
 186  
             //Allow overrides to alter the message if necessary
 187  0
             processMessage(msg, event);
 188  
 
 189  
             // QoS support
 190  0
             long ttl = eventMsg.getOutboundProperty(JmsConstants.TIME_TO_LIVE_PROPERTY, Message.DEFAULT_TIME_TO_LIVE);
 191  0
             int priority = eventMsg.getOutboundProperty(JmsConstants.PRIORITY_PROPERTY, Message.DEFAULT_PRIORITY);
 192  0
             boolean persistent= eventMsg.getOutboundProperty(JmsConstants.PERSISTENT_DELIVERY_PROPERTY, connector.isPersistentDelivery());
 193  
 
 194  
             // If we are honouring the current QoS message headers we need to use the ones set on the current message
 195  0
             if (connector.isHonorQosHeaders())
 196  
             {
 197  0
                 Object priorityProp = eventMsg.getInboundProperty(JmsConstants.JMS_PRIORITY);
 198  0
                 Object deliveryModeProp = eventMsg.getInboundProperty(JmsConstants.JMS_DELIVERY_MODE);
 199  
 
 200  0
                 if (priorityProp != null)
 201  
                 {
 202  0
                     priority = NumberUtils.toInt(priorityProp);
 203  
                 }
 204  0
                 if (deliveryModeProp != null)
 205  
                 {
 206  0
                     persistent = NumberUtils.toInt(deliveryModeProp) == DeliveryMode.PERSISTENT;
 207  
                 }
 208  
             }
 209  
 
 210  0
             if (logger.isDebugEnabled())
 211  
             {
 212  0
                 logger.debug("Sending message of type " + ClassUtils.getSimpleName(msg.getClass()));
 213  0
                 logger.debug("Sending JMS Message type " + msg.getJMSType() +
 214  
                        "\n  JMSMessageID=" + msg.getJMSMessageID() +
 215  
                        "\n  JMSCorrelationID=" + msg.getJMSCorrelationID() +
 216  
                        "\n  JMSDeliveryMode=" + (persistent ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT) +
 217  
                        "\n  JMSPriority=" + priority +
 218  
                        "\n  JMSReplyTo=" + msg.getJMSReplyTo());
 219  
             }
 220  0
             connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic, endpoint);
 221  
 
 222  0
             if (useReplyToDestination && replyTo != null)
 223  
             {
 224  0
                 consumer = createReplyToConsumer(msg, event, session, replyTo, topic);
 225  
 
 226  0
                 if (topic)
 227  
                 {
 228  
                     // need to register a listener for a topic
 229  0
                     Latch l = new Latch();
 230  0
                     ReplyToListener listener = new ReplyToListener(l);
 231  0
                     consumer.setMessageListener(listener);
 232  
 
 233  0
                     connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic, endpoint);
 234  
 
 235  0
                     int timeout = event.getTimeout();
 236  
 
 237  0
                     if (logger.isDebugEnabled())
 238  
                     {
 239  0
                         logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
 240  
                     }
 241  
 
 242  0
                     l.await(timeout, TimeUnit.MILLISECONDS);
 243  0
                     consumer.setMessageListener(null);
 244  0
                     listener.release();
 245  0
                     Message result = listener.getMessage();
 246  0
                     if (result == null)
 247  
                     {
 248  0
                         logger.debug("No message was returned via replyTo destination");
 249  0
                         return createNullMuleMessage();
 250  
                     }
 251  
                     else
 252  
                     {
 253  0
                         return createMessageWithJmsMessagePayload(result);
 254  
                     }
 255  
                 }
 256  
                 else
 257  
                 {
 258  0
                     int timeout = event.getTimeout();
 259  
 
 260  0
                     if (logger.isDebugEnabled())
 261  
                     {
 262  0
                         logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
 263  
                     }
 264  
 
 265  0
                     Message result = consumer.receive(timeout);
 266  0
                     if (result == null)
 267  
                     {
 268  0
                         logger.debug("No message was returned via replyTo destination " + replyTo);
 269  0
                         return createNullMuleMessage();
 270  
                     }
 271  
                     else
 272  
                     {
 273  0
                         return createMessageWithJmsMessagePayload(result);
 274  
                     }
 275  
                 }
 276  
             }
 277  
             else
 278  
             {
 279  
                 // In this case a response was never expected so we return null and not NullPayload.
 280  
                 // This generally happens when dispatch is used for an asynchronous endpoint but can also occur when send() is used 
 281  
                 // and disableTempDestinations is set.
 282  0
                 return returnOriginalMessageAsReply ? createMuleMessage(msg) : null;
 283  
             }
 284  
         }
 285  
         finally
 286  
         {
 287  0
             connector.closeQuietly(producer);
 288  0
             connector.closeQuietly(consumer);
 289  
 
 290  
             // TODO AP check if TopicResolver is to be utilized for temp destinations as well
 291  0
             if (replyTo != null && (replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic))
 292  
             {
 293  0
                 if (replyTo instanceof TemporaryQueue)
 294  
                 {
 295  0
                     connector.closeQuietly((TemporaryQueue) replyTo);
 296  
                 }
 297  
                 else
 298  
                 {
 299  
                     // hope there are no more non-standard tricks from JMS vendors
 300  
                     // here ;)
 301  0
                     connector.closeQuietly((TemporaryTopic) replyTo);
 302  
                 }
 303  
             }
 304  
 
 305  0
             if (!sessionManaged && transacted) { 
 306  0
                 handleMultiTx(session);
 307  
             }
 308  
 
 309  
 
 310  
             // If the session is from the current transaction, it is up to the
 311  
             // transaction to close it.
 312  0
             if (session != null && !cached && !transacted)
 313  
             {
 314  0
                 connector.closeQuietly(session);
 315  
             }
 316  
         }
 317  
     }
 318  
 
 319  
     protected MuleMessage createMessageWithJmsMessagePayload(Message jmsMessage) throws Exception
 320  
     {
 321  0
         MuleMessage muleMessage = createMuleMessage(jmsMessage);
 322  0
         Object payload = JmsMessageUtils.toObject(jmsMessage, connector.getSpecification(),
 323  
             endpoint.getEncoding());
 324  0
         muleMessage.setPayload(payload);
 325  0
         return muleMessage;
 326  
     }
 327  
     
 328  
     /**
 329  
      * This method is called before the current message is transformed.  It can be used to do any message body or
 330  
      * header processing before the transformer is called.
 331  
      *
 332  
      * @param message the current MuleMessage Being processed
 333  
      * @throws Exception
 334  
      */
 335  
     protected void preTransformMessage(MuleMessage message) throws Exception
 336  
     {
 337  
         // nothing to do
 338  0
     }
 339  
     
 340  
     protected void handleMultiTx(Session session) throws Exception
 341  
     {
 342  0
         logger.debug("Multi-transaction support is not available in Mule Community Edition.");
 343  0
     }
 344  
 
 345  
     @Override
 346  
     protected MuleMessage doSend(MuleEvent event) throws Exception
 347  
     {
 348  0
         return dispatchMessage(event, true);
 349  
     }
 350  
 
 351  
     @Override
 352  
     protected void doDispose()
 353  
     {
 354  
         // template method
 355  0
     }
 356  
 
 357  
     /**
 358  
      * This method is called once the JMS message is created.  It allows subclasses to alter the
 359  
      * message if necessary.
 360  
      *
 361  
      * @param msg   The JMS message that will be sent
 362  
      * @param event the current event
 363  
      * @throws JMSException if the JmsMessage cannot be written to, this should not happen because 
 364  
      *          the JMSMessage passed in will always be newly created
 365  
      */
 366  
     protected void processMessage(Message msg, MuleEvent event) throws JMSException
 367  
     {
 368  
         // template Method
 369  0
     }
 370  
 
 371  
     /**
 372  
      * Some JMS implementations do not support ReplyTo or require some further fiddling of the message
 373  
      *
 374  
      * @param msg   The JMS message that will be sent
 375  
      * @param event the current event
 376  
      * @return true if this request should honour any JMSReplyTo settings on the message
 377  
      * @throws JMSException if the JmsMessage cannot be written to, this should not happen because the JMSMessage passed
 378  
      *                      in will always be newly created
 379  
      */
 380  
     protected boolean isHandleReplyTo(Message msg, MuleEvent event) throws JMSException
 381  
     {
 382  0
         return connector.supportsProperty(JmsConstants.JMS_REPLY_TO);
 383  
     }
 384  
 
 385  
     protected MessageConsumer createReplyToConsumer(Message currentMessage, MuleEvent event,
 386  
                                                     Session session, Destination replyTo, boolean topic) throws JMSException
 387  
     {
 388  0
         String selector = null;
 389  
         //Only used by topics
 390  
         String durableName;
 391  
         //If we're not using
 392  0
         if (!(replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic))
 393  
         {
 394  0
             String jmsCorrelationId = currentMessage.getJMSCorrelationID();
 395  0
             if (jmsCorrelationId == null)
 396  
             {
 397  0
                 jmsCorrelationId = currentMessage.getJMSMessageID();
 398  
             }
 399  
 
 400  0
             selector = "JMSCorrelationID='" + jmsCorrelationId + "'";
 401  0
             if (logger.isDebugEnabled())
 402  
             {
 403  0
                 logger.debug("ReplyTo Selector is: " + selector);
 404  
             }
 405  
         }
 406  
 
 407  
         //We need to set the durableName and Selector if using topics
 408  0
         if (topic)
 409  
         {
 410  0
             String tempDurable = (String) event.getEndpoint().getProperties().get(JmsConstants.DURABLE_PROPERTY);
 411  0
             boolean durable = connector.isDurable();
 412  0
             if (tempDurable != null)
 413  
             {
 414  0
                 durable = Boolean.valueOf(tempDurable);
 415  
             }
 416  
             // Get the durable subscriber name if there is one
 417  0
             durableName = (String) event.getEndpoint().getProperties().get(
 418  
                     JmsConstants.DURABLE_NAME_PROPERTY);
 419  0
             if (durableName == null && durable && topic)
 420  
             {
 421  0
                 durableName = "mule." + connector.getName() + "." + event.getEndpoint().getEndpointURI().getAddress();
 422  0
                 if (logger.isDebugEnabled())
 423  
                 {
 424  0
                     logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: " +
 425  
                                  durableName);
 426  
                 }
 427  
             }
 428  
         }
 429  0
         return connector.getJmsSupport().createConsumer(session, replyTo, selector,
 430  
                                                         connector.isNoLocal(), null, topic, endpoint);
 431  
     }
 432  
 
 433  
     protected Destination getReplyToDestination(Message message, Session session, MuleEvent event, boolean remoteSync, boolean topic) throws JMSException, EndpointException, InitialisationException
 434  
     {
 435  0
         Destination replyTo = null;
 436  
 
 437  
         // Some JMS implementations might not support the ReplyTo property.
 438  0
         if (isHandleReplyTo(message, event))
 439  
         {
 440  
 
 441  0
             Object tempReplyTo = event.getMessage().getOutboundProperty(JmsConstants.JMS_REPLY_TO);
 442  0
             if (tempReplyTo == null)
 443  
             {
 444  
                 //It may be a Mule URI or global endpoint Ref
 445  0
                 tempReplyTo = event.getMessage().getOutboundProperty(MuleProperties.MULE_REPLY_TO_PROPERTY);
 446  0
                 if (tempReplyTo != null)
 447  
                 {
 448  0
                     int i = tempReplyTo.toString().indexOf("://");
 449  0
                     if (i > -1)
 450  
                     {
 451  0
                         tempReplyTo = tempReplyTo.toString().substring(i+3);
 452  
                     }
 453  
                     else
 454  
                     {
 455  0
                         EndpointBuilder epb = event.getMuleContext().getRegistry().lookupEndpointBuilder(tempReplyTo.toString());
 456  0
                         if (epb != null)
 457  
                         {
 458  0
                             tempReplyTo = epb.buildOutboundEndpoint().getEndpointURI().getAddress();
 459  
                         }
 460  
                     }
 461  
                 }
 462  
             }
 463  0
             if (tempReplyTo != null)
 464  
             {
 465  0
                 if (tempReplyTo instanceof Destination)
 466  
                 {
 467  0
                     replyTo = (Destination) tempReplyTo;
 468  
                 }
 469  
                 else
 470  
                 {
 471  
                     // TODO AP should this drill-down be moved into the resolver as well?
 472  0
                     boolean replyToTopic = false;
 473  0
                     String reply = tempReplyTo.toString();
 474  0
                     int i = reply.indexOf(":");
 475  0
                     if (i > -1)
 476  
                     {
 477  
                         // TODO MULE-1409 this check will not work for ActiveMQ 4.x,
 478  
                         // as they have temp-queue://<destination> and temp-topic://<destination> URIs
 479  
                         // Extract to a custom resolver for ActiveMQ4.x
 480  
                         // The code path can be exercised, e.g. by a LoanBrokerESBTestCase
 481  0
                         String qtype = reply.substring(0, i);
 482  0
                         replyToTopic = JmsConstants.TOPIC_PROPERTY.equalsIgnoreCase(qtype);
 483  0
                         reply = reply.substring(i + 1);
 484  
                     }
 485  0
                     replyTo = connector.getJmsSupport().createDestination(session, reply, replyToTopic, endpoint);
 486  
                 }
 487  
             }
 488  
             // Are we going to wait for a return event ?
 489  0
             if (remoteSync && replyTo == null && !disableTemporaryDestinations)
 490  
             {
 491  0
                 replyTo = connector.getJmsSupport().createTemporaryDestination(session, topic);
 492  
             }
 493  
         }
 494  0
         return replyTo;
 495  
 
 496  
     }
 497  
 
 498  
     protected class ReplyToListener implements MessageListener
 499  
     {
 500  
         private final Latch latch;
 501  
         private volatile Message message;
 502  0
         private final WaitableBoolean released = new WaitableBoolean(false);
 503  
 
 504  
         public ReplyToListener(Latch latch)
 505  0
         {
 506  0
             this.latch = latch;
 507  0
         }
 508  
 
 509  
         public Message getMessage()
 510  
         {
 511  0
             return message;
 512  
         }
 513  
 
 514  
         public void release()
 515  
         {
 516  0
             released.set(true);
 517  0
         }
 518  
 
 519  
         public void onMessage(Message message)
 520  
         {
 521  0
             this.message = message;
 522  0
             latch.countDown();
 523  
             try
 524  
             {
 525  0
                 released.whenTrue(null);
 526  
             }
 527  0
             catch (InterruptedException e)
 528  
             {
 529  
                 // ignored
 530  0
             }
 531  0
         }
 532  
     }
 533  
     
 534  
     @Override
 535  
     protected void applyOutboundTransformers(MuleEvent event) throws MuleException
 536  
     {
 537  
         try
 538  
         {
 539  0
             preTransformMessage(event.getMessage());
 540  
         }
 541  0
         catch (Exception e)
 542  
         {
 543  0
             throw new TransformerException(CoreMessages.failedToInvoke("preTransformMessage"), e);
 544  0
         }
 545  0
         super.applyOutboundTransformers(event);
 546  0
     }
 547  
 
 548  
 }