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