Coverage Report - org.mule.providers.jms.JmsMessageDispatcher
 
Classes in this File Line Coverage Branch Coverage Complexity
JmsMessageDispatcher
63%
95/150
48%
51/106
5
JmsMessageDispatcher$ReplyToListener
0%
0/13
N/A
5
 
 1  
 /*
 2  
  * $Id: JmsMessageDispatcher.java 11728 2008-05-13 07:31:11Z dirk.olmes $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.providers.jms;
 12  
 
 13  
 import org.mule.impl.MuleMessage;
 14  
 import org.mule.providers.AbstractMessageDispatcher;
 15  
 import org.mule.providers.jms.filters.JmsSelectorFilter;
 16  
 import org.mule.providers.jms.i18n.JmsMessages;
 17  
 import org.mule.transaction.IllegalTransactionStateException;
 18  
 import org.mule.umo.UMOEvent;
 19  
 import org.mule.umo.UMOMessage;
 20  
 import org.mule.umo.endpoint.UMOEndpointURI;
 21  
 import org.mule.umo.endpoint.UMOImmutableEndpoint;
 22  
 import org.mule.umo.provider.DispatchException;
 23  
 import org.mule.umo.provider.UMOConnector;
 24  
 import org.mule.umo.provider.UMOMessageAdapter;
 25  
 import org.mule.util.ClassUtils;
 26  
 import org.mule.util.NumberUtils;
 27  
 import org.mule.util.StringUtils;
 28  
 import org.mule.util.concurrent.Latch;
 29  
 import org.mule.util.concurrent.WaitableBoolean;
 30  
 
 31  
 import javax.jms.DeliveryMode;
 32  
 import javax.jms.Destination;
 33  
 import javax.jms.Message;
 34  
 import javax.jms.MessageConsumer;
 35  
 import javax.jms.MessageListener;
 36  
 import javax.jms.MessageProducer;
 37  
 import javax.jms.Session;
 38  
 import javax.jms.TemporaryQueue;
 39  
 import javax.jms.TemporaryTopic;
 40  
 
 41  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 42  
 
 43  
 import org.apache.commons.lang.BooleanUtils;
 44  
 
 45  
 /**
 46  
  * <code>JmsMessageDispatcher</code> is responsible for dispatching messages to JMS
 47  
  * destinations. All JMS semantics apply and settings such as replyTo and QoS
 48  
  * properties are read from the event properties or defaults are used (according to
 49  
  * the JMS specification)
 50  
  */
 51  
 public class JmsMessageDispatcher extends AbstractMessageDispatcher
 52  
 {
 53  
 
 54  
     private JmsConnector connector;
 55  
     private Session cachedSession;
 56  
 
 57  
     public JmsMessageDispatcher(UMOImmutableEndpoint endpoint)
 58  
     {
 59  74
         super(endpoint);
 60  74
         this.connector = (JmsConnector) endpoint.getConnector();
 61  74
     }
 62  
 
 63  
     protected void doDispatch(UMOEvent event) throws Exception
 64  
     {
 65  29
         dispatchMessage(event);
 66  27
     }
 67  
 
 68  
     protected void doConnect() throws Exception
 69  
     {
 70  
         // template method
 71  74
     }
 72  
 
 73  
     protected void doDisconnect() throws Exception
 74  
     {
 75  
         // template method
 76  74
     }
 77  
 
 78  
     private UMOMessage dispatchMessage(UMOEvent event) throws Exception
 79  
     {
 80  75
         Session session = null;
 81  75
         MessageProducer producer = null;
 82  75
         MessageConsumer consumer = null;
 83  75
         Destination replyTo = null;
 84  75
         boolean transacted = false;
 85  76
         boolean cached = false;
 86  76
         boolean remoteSync = useRemoteSync(event);
 87  
 
 88  75
         if (logger.isDebugEnabled())
 89  
         {
 90  0
             logger.debug("dispatching on endpoint: " + event.getEndpoint().getEndpointURI()
 91  
                          + ". Event id is: " + event.getId());
 92  
         }
 93  
 
 94  
         try
 95  
         {
 96  76
             session = connector.getSessionFromTransaction();
 97  75
             if (session != null)
 98  
             {
 99  20
                 transacted = true;
 100  
 
 101  
                 // If a transaction is running, we can not receive any messages
 102  
                 // in the same transaction.
 103  20
                 if (remoteSync)
 104  
                 {
 105  0
                     throw new IllegalTransactionStateException(
 106  
                         JmsMessages.connectorDoesNotSupportSyncReceiveWhenTransacted());
 107  
                 }
 108  
             }
 109  
             // Should we be caching sessions? Note this is not part of the JMS spec.
 110  
             // and is turned off by default.
 111  56
             else if (event.getMessage().getBooleanProperty(JmsConstants.CACHE_JMS_SESSIONS_PROPERTY,
 112  
                 connector.isCacheJmsSessions()))
 113  
             {
 114  0
                 cached = true;
 115  0
                 if (cachedSession != null)
 116  
                 {
 117  0
                     session = cachedSession;
 118  
                 }
 119  
                 else
 120  
                 {
 121  0
                     session = connector.getSession(event.getEndpoint());
 122  0
                     cachedSession = session;
 123  
                 }
 124  
             }
 125  
             else
 126  
             {
 127  55
                 session = connector.getSession(event.getEndpoint());
 128  56
                 if (event.getEndpoint().getTransactionConfig().isTransacted())
 129  
                 {
 130  14
                     transacted = true;
 131  
                 }
 132  
             }
 133  
 
 134  76
             UMOEndpointURI endpointUri = event.getEndpoint().getEndpointURI();
 135  
 
 136  76
             boolean topic = connector.getTopicResolver().isTopic(event.getEndpoint(), true);
 137  
 
 138  76
             Destination dest = connector.getJmsSupport().createDestination(session, endpointUri.getAddress(),
 139  
                 topic);
 140  76
             producer = connector.getJmsSupport().createProducer(session, dest, topic);
 141  
 
 142  76
             Object message = event.getTransformedMessage();
 143  74
             if (!(message instanceof Message))
 144  
             {
 145  0
                 throw new DispatchException(
 146  
                     JmsMessages.checkTransformer("JMS message", message.getClass(), connector.getName()),
 147  
                     event.getMessage(), event.getEndpoint());
 148  
             }
 149  
 
 150  74
             Message msg = (Message) message;
 151  73
             if (event.getMessage().getCorrelationId() != null)
 152  
             {
 153  65
                 msg.setJMSCorrelationID(event.getMessage().getCorrelationId());
 154  
             }
 155  
 
 156  73
             UMOMessage eventMsg = event.getMessage();
 157  
 
 158  
             // Some JMS implementations might not support the ReplyTo property.
 159  74
             if (connector.supportsProperty(JmsConstants.JMS_REPLY_TO))
 160  
             {
 161  74
                 Object tempReplyTo = eventMsg.removeProperty(JmsConstants.JMS_REPLY_TO);
 162  74
                 if (tempReplyTo != null)
 163  
                 {
 164  4
                     if (tempReplyTo instanceof Destination)
 165  
                     {
 166  4
                         replyTo = (Destination) tempReplyTo;
 167  
                     }
 168  
                     else
 169  
                     {
 170  
                         // TODO AP should this drill-down be moved into the resolver as well?
 171  0
                         boolean replyToTopic = false;
 172  0
                         String reply = tempReplyTo.toString();
 173  0
                         int i = reply.indexOf(":");
 174  0
                         if (i > -1)
 175  
                         {
 176  
                             // TODO MULE-1409 this check will not work for ActiveMQ 4.x,
 177  
                             // as they have temp-queue://<destination> and temp-topic://<destination> URIs
 178  
                             // Extract to a custom resolver for ActiveMQ4.x
 179  
                             // The code path can be exercised, e.g. by a LoanBrokerESBTestCase
 180  0
                             String qtype = reply.substring(0, i);
 181  0
                             replyToTopic = JmsConstants.TOPIC_PROPERTY.equalsIgnoreCase(qtype);
 182  0
                             reply = reply.substring(i + 1);
 183  
                         }
 184  0
                         replyTo = connector.getJmsSupport().createDestination(session, reply, replyToTopic);
 185  
                     }
 186  
                 }
 187  
                 // Are we going to wait for a return event ?
 188  73
                 if (remoteSync && replyTo == null)
 189  
                 {
 190  4
                     replyTo = connector.getJmsSupport().createTemporaryDestination(session, topic);
 191  
                 }
 192  
                 // Set the replyTo property
 193  74
                 if (replyTo != null)
 194  
                 {
 195  8
                     msg.setJMSReplyTo(replyTo);
 196  
                 }
 197  
 
 198  
                 // Are we going to wait for a return event ?
 199  73
                 if (remoteSync)
 200  
                 {
 201  4
                     consumer = connector.getJmsSupport().createConsumer(session, replyTo, topic);
 202  
                 }
 203  
             }
 204  
 
 205  
             // QoS support
 206  74
             String ttlString = (String) eventMsg.removeProperty(JmsConstants.TIME_TO_LIVE_PROPERTY);
 207  74
             String priorityString = (String) eventMsg.removeProperty(JmsConstants.PRIORITY_PROPERTY);
 208  74
             String persistentDeliveryString = (String) eventMsg.removeProperty(JmsConstants.PERSISTENT_DELIVERY_PROPERTY);
 209  
 
 210  73
             long ttl = StringUtils.isNotBlank(ttlString)
 211  
                                 ? NumberUtils.toLong(ttlString)
 212  
                                 : Message.DEFAULT_TIME_TO_LIVE;
 213  72
             int priority = StringUtils.isNotBlank(priorityString)
 214  
                                 ? NumberUtils.toInt(priorityString)
 215  
                                 : Message.DEFAULT_PRIORITY;
 216  72
             boolean persistent = StringUtils.isNotBlank(persistentDeliveryString)
 217  
                                 ? BooleanUtils.toBoolean(persistentDeliveryString)
 218  
                                 : connector.isPersistentDelivery();
 219  
 
 220  72
             if (connector.isHonorQosHeaders())
 221  
             {
 222  0
                 int priorityProp = eventMsg.getIntProperty(JmsConstants.JMS_PRIORITY, UMOConnector.INT_VALUE_NOT_SET);
 223  0
                 int deliveryModeProp = eventMsg.getIntProperty(JmsConstants.JMS_DELIVERY_MODE, UMOConnector.INT_VALUE_NOT_SET);
 224  
 
 225  0
                 if (priorityProp != UMOConnector.INT_VALUE_NOT_SET)
 226  
                 {
 227  0
                     priority = priorityProp;
 228  
                 }
 229  0
                 if (deliveryModeProp != UMOConnector.INT_VALUE_NOT_SET)
 230  
                 {
 231  0
                     persistent = deliveryModeProp == DeliveryMode.PERSISTENT;
 232  
                 }
 233  
             }
 234  
 
 235  73
             if (logger.isDebugEnabled())
 236  
             {
 237  0
                 logger.debug("Sending message of type " + ClassUtils.getSimpleName(msg.getClass()));
 238  
             }
 239  
 
 240  73
             if (consumer != null && topic)
 241  
             {
 242  
                 // need to register a listener for a topic
 243  0
                 Latch l = new Latch();
 244  0
                 ReplyToListener listener = new ReplyToListener(l);
 245  0
                 consumer.setMessageListener(listener);
 246  
 
 247  0
                 connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic);
 248  
 
 249  0
                 int timeout = event.getTimeout();
 250  
 
 251  0
                 if (logger.isDebugEnabled())
 252  
                 {
 253  0
                     logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
 254  
                 }
 255  
 
 256  0
                 l.await(timeout, TimeUnit.MILLISECONDS);
 257  0
                 consumer.setMessageListener(null);
 258  0
                 listener.release();
 259  0
                 Message result = listener.getMessage();
 260  0
                 if (result == null)
 261  
                 {
 262  0
                     logger.debug("No message was returned via replyTo destination");
 263  0
                     return null;
 264  
                 }
 265  
                 else
 266  
                 {
 267  0
                     UMOMessageAdapter adapter = connector.getMessageAdapter(result);
 268  0
                     return new MuleMessage(JmsMessageUtils.toObject(result, connector.getSpecification()),
 269  
                         adapter);
 270  
                 }
 271  
             }
 272  
             else
 273  
             {
 274  73
                 connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic);
 275  73
                 if (consumer != null)
 276  
                 {
 277  4
                     int timeout = event.getTimeout();
 278  
 
 279  4
                     if (logger.isDebugEnabled())
 280  
                     {
 281  0
                         logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
 282  
                     }
 283  
 
 284  4
                     Message result = consumer.receive(timeout);
 285  4
                     if (result == null)
 286  
                     {
 287  0
                         logger.debug("No message was returned via replyTo destination");
 288  0
                         return null;
 289  
                     }
 290  
                     else
 291  
                     {
 292  4
                         UMOMessageAdapter adapter = connector.getMessageAdapter(result);
 293  4
                         return new MuleMessage(
 294  
                             JmsMessageUtils.toObject(result, connector.getSpecification()), adapter);
 295  
                     }
 296  
                 }
 297  
             }
 298  69
             return null;
 299  
         }
 300  
         finally
 301  
         {
 302  3
             connector.closeQuietly(producer);
 303  76
             connector.closeQuietly(consumer);
 304  
 
 305  
             // TODO AP check if TopicResolver is to be utilized for temp destinations as well
 306  76
             if (replyTo != null && (replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic))
 307  
             {
 308  4
                 if (replyTo instanceof TemporaryQueue)
 309  
                 {
 310  4
                     connector.closeQuietly((TemporaryQueue) replyTo);
 311  
                 }
 312  
                 else
 313  
                 {
 314  
                     // hope there are no more non-standard tricks from JMS vendors
 315  
                     // here ;)
 316  0
                     connector.closeQuietly((TemporaryTopic) replyTo);
 317  
                 }
 318  
             }
 319  
 
 320  
             // If the session is from the current transaction, it is up to the
 321  
             // transaction to close it.
 322  76
             if (session != null && !cached && !transacted)
 323  
             {
 324  42
                 connector.closeQuietly(session);
 325  
             }
 326  
         }
 327  
     }
 328  
 
 329  
     protected UMOMessage doSend(UMOEvent event) throws Exception
 330  
     {
 331  46
         UMOMessage message = dispatchMessage(event);
 332  46
         return message;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Make a specific request to the underlying transport
 337  
      * 
 338  
      * @param timeout the maximum time the operation should block before returning.
 339  
      *            The call should return immediately if there is data available. If
 340  
      *            no data becomes available before the timeout elapses, null will be
 341  
      *            returned
 342  
      * @return the result of the request wrapped in a UMOMessage object. Null will be
 343  
      *         returned if no data was avaialable
 344  
      * @throws Exception if the call to the underlying protocal cuases an exception
 345  
      */
 346  
     protected UMOMessage doReceive(long timeout) throws Exception
 347  
     {
 348  6
         Session session = null;
 349  6
         MessageConsumer consumer = null;
 350  
 
 351  
         try
 352  
         {
 353  6
             final boolean topic = connector.getTopicResolver().isTopic(endpoint);
 354  
 
 355  6
             JmsSupport support = connector.getJmsSupport();
 356  6
             session = connector.getSession(false, topic);
 357  6
             Destination dest = support.createDestination(session, endpoint.getEndpointURI().getAddress(),
 358  
                 topic);
 359  
 
 360  
             // Extract jms selector
 361  6
             String selector = null;
 362  6
             if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
 363  
             {
 364  0
                 selector = ((JmsSelectorFilter) endpoint.getFilter()).getExpression();
 365  
             }
 366  6
             else if (endpoint.getProperties() != null)
 367  
             {
 368  
                 // still allow the selector to be set as a property on the endpoint
 369  
                 // to be backward compatable
 370  6
                 selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
 371  
             }
 372  6
             String tempDurable = (String) endpoint.getProperties().get(JmsConstants.DURABLE_PROPERTY);
 373  6
             boolean durable = connector.isDurable();
 374  6
             if (tempDurable != null)
 375  
             {
 376  0
                 durable = Boolean.valueOf(tempDurable).booleanValue();
 377  
             }
 378  
 
 379  
             // Get the durable subscriber name if there is one
 380  6
             String durableName = (String) endpoint.getProperties().get(JmsConstants.DURABLE_NAME_PROPERTY);
 381  6
             if (durableName == null && durable && topic)
 382  
             {
 383  0
                 durableName = "mule." + connector.getName() + "." + endpoint.getEndpointURI().getAddress();
 384  0
                 if (logger.isDebugEnabled())
 385  
                 {
 386  0
                     logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
 387  
                              + durableName);
 388  
                 }
 389  
             }
 390  
 
 391  
             // Create consumer
 392  6
             consumer = support.createConsumer(session, dest, selector, connector.isNoLocal(), durableName,
 393  
                 topic);
 394  
 
 395  
             try
 396  
             {
 397  
                 Message message;
 398  
 
 399  6
                 if (timeout == RECEIVE_NO_WAIT)
 400  
                 {
 401  0
                     message = consumer.receiveNoWait();
 402  
                 }
 403  6
                 else if (timeout == RECEIVE_WAIT_INDEFINITELY)
 404  
                 {
 405  0
                     message = consumer.receive();
 406  
                 }
 407  
                 else
 408  
                 {
 409  6
                     message = consumer.receive(timeout);
 410  
                 }
 411  
 
 412  6
                 if (message == null)
 413  
                 {
 414  0
                     return null;
 415  
                 }
 416  
 
 417  6
                 message = connector.preProcessMessage(message, session);
 418  
 
 419  6
                 return new MuleMessage(connector.getMessageAdapter(message));
 420  
             }
 421  0
             catch (Exception e)
 422  
             {
 423  0
                 connector.handleException(e);
 424  0
                 return null;
 425  
             }
 426  
         }
 427  
         finally
 428  
         {
 429  0
             connector.closeQuietly(consumer);
 430  6
             connector.closeQuietly(session);
 431  
         }
 432  
     }
 433  
 
 434  
     protected void doDispose()
 435  
     {
 436  
         // template method
 437  74
     }
 438  
 
 439  
     private class ReplyToListener implements MessageListener
 440  
     {
 441  
         private final Latch latch;
 442  
         private volatile Message message;
 443  0
         private final WaitableBoolean released = new WaitableBoolean(false);
 444  
 
 445  
         public ReplyToListener(Latch latch)
 446  0
         {
 447  0
             this.latch = latch;
 448  0
         }
 449  
 
 450  
         public Message getMessage()
 451  
         {
 452  0
             return message;
 453  
         }
 454  
 
 455  
         public void release()
 456  
         {
 457  0
             released.set(true);
 458  0
         }
 459  
 
 460  
         public void onMessage(Message message)
 461  
         {
 462  0
             this.message = message;
 463  0
             latch.countDown();
 464  
             try
 465  
             {
 466  0
                 released.whenTrue(null);
 467  
             }
 468  0
             catch (InterruptedException e)
 469  
             {
 470  
                 // ignored
 471  0
             }
 472  0
         }
 473  
     }
 474  
 
 475  
 }