View Javadoc

1   /*
2    * $Id: JmsMessageDispatcher.java 19334 2010-09-03 14:27:04Z rossmason $
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      private boolean disableTemporaryDestinations = false;
58      private boolean returnOriginalMessageAsReply = false;
59  
60      public JmsMessageDispatcher(OutboundEndpoint endpoint)
61      {
62          super(endpoint);
63          this.connector = (JmsConnector) endpoint.getConnector();
64          disableTemporaryDestinations = connector.isDisableTemporaryReplyToDestinations() ||
65              ("true".equals(endpoint.getProperty(JmsConstants.DISABLE_TEMP_DESTINATIONS_PROPERTY)));
66          returnOriginalMessageAsReply = connector.isReturnOriginalMessageAsReply() ||
67              ("true".equals(endpoint.getProperty(JmsConstants.RETURN_ORIGINAL_MESSAGE_PROPERTY)));
68          if (returnOriginalMessageAsReply && !disableTemporaryDestinations)
69          {
70              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      }
73  
74      @Override
75      protected void doDispatch(MuleEvent event) throws Exception
76      {
77          if (connector.getConnection() == null)
78          {
79              throw new IllegalStateException("No JMS Connection");
80          }
81          dispatchMessage(event, false);
82      }
83  
84      @Override
85      protected void doConnect() throws Exception
86      {
87          // template method
88      }
89  
90      @Override
91      protected void doDisconnect() throws Exception
92      {
93          // template method
94      }
95  
96      protected boolean isDisableTemporaryDestinations()
97      {
98          return disableTemporaryDestinations;
99      }
100 
101     private MuleMessage dispatchMessage(MuleEvent event, boolean doSend) throws Exception
102     {
103         Session session = null;
104         MessageProducer producer = null;
105         MessageConsumer consumer = null;
106         Destination replyTo = null;
107         boolean transacted = false;
108         boolean cached = false;
109         boolean useReplyToDestination;
110 
111         final Transaction muleTx = TransactionCoordination.getInstance().getTransaction();
112 
113         if (logger.isDebugEnabled())
114         {
115             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         boolean sessionManaged = true;
122         try
123         {
124             session = connector.getSessionFromTransaction();
125             if (session != null)
126             {
127                 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             else if (event.getMessage().getOutboundProperty(JmsConstants.CACHE_JMS_SESSIONS_PROPERTY, connector.isCacheJmsSessions()))
132             {
133                 sessionManaged = false;
134                 cached = true;
135                 if (cachedSession != null)
136                 {
137                     session = cachedSession;
138                 }
139                 else
140                 {
141                     session = connector.getSession(event.getEndpoint());
142                     cachedSession = session;
143                 }
144             }
145             else
146             {
147                 // by now we're running with a different connector and connection
148                 sessionManaged = muleTx != null && muleTx.isXA();
149 
150                 session = connector.getSession(event.getEndpoint());
151                 if (event.getEndpoint().getTransactionConfig().isTransacted())
152                 {
153                     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             useReplyToDestination = returnResponse(event, doSend) && !transacted;
160 
161             boolean topic = connector.getTopicResolver().isTopic(event.getEndpoint(), true);
162 
163             Destination dest = connector.getJmsSupport().createDestination(session, endpoint);
164             producer = connector.getJmsSupport().createProducer(session, dest, topic);
165 
166             Object message = event.getMessage().getPayload();
167             if (!(message instanceof Message))
168             {
169                 throw new DispatchException(
170                         JmsMessages.checkTransformer("JMS message", message.getClass(), connector.getName()),
171                         event, (OutboundEndpoint) endpoint);
172             }
173 
174             Message msg = (Message) message;
175 
176             MuleMessage eventMsg = event.getMessage();
177 
178             replyTo = getReplyToDestination(msg, session, event, useReplyToDestination, topic);
179 
180             // Set the replyTo property
181             if (replyTo != null)
182             {
183                 msg.setJMSReplyTo(replyTo);
184             }
185 
186             //Allow overrides to alter the message if necessary
187             processMessage(msg, event);
188 
189             // QoS support
190             long ttl = eventMsg.getOutboundProperty(JmsConstants.TIME_TO_LIVE_PROPERTY, Message.DEFAULT_TIME_TO_LIVE);
191             int priority = eventMsg.getOutboundProperty(JmsConstants.PRIORITY_PROPERTY, Message.DEFAULT_PRIORITY);
192             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             if (connector.isHonorQosHeaders())
196             {
197                 Object priorityProp = eventMsg.getInboundProperty(JmsConstants.JMS_PRIORITY);
198                 Object deliveryModeProp = eventMsg.getInboundProperty(JmsConstants.JMS_DELIVERY_MODE);
199 
200                 if (priorityProp != null)
201                 {
202                     priority = NumberUtils.toInt(priorityProp);
203                 }
204                 if (deliveryModeProp != null)
205                 {
206                     persistent = NumberUtils.toInt(deliveryModeProp) == DeliveryMode.PERSISTENT;
207                 }
208             }
209 
210             if (logger.isDebugEnabled())
211             {
212                 logger.debug("Sending message of type " + ClassUtils.getSimpleName(msg.getClass()));
213                 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             connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic, endpoint);
221 
222             if (useReplyToDestination && replyTo != null)
223             {
224                 consumer = createReplyToConsumer(msg, event, session, replyTo, topic);
225 
226                 if (topic)
227                 {
228                     // need to register a listener for a topic
229                     Latch l = new Latch();
230                     ReplyToListener listener = new ReplyToListener(l);
231                     consumer.setMessageListener(listener);
232 
233                     connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic, endpoint);
234 
235                     int timeout = event.getTimeout();
236 
237                     if (logger.isDebugEnabled())
238                     {
239                         logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
240                     }
241 
242                     l.await(timeout, TimeUnit.MILLISECONDS);
243                     consumer.setMessageListener(null);
244                     listener.release();
245                     Message result = listener.getMessage();
246                     if (result == null)
247                     {
248                         logger.debug("No message was returned via replyTo destination");
249                         return createNullMuleMessage();
250                     }
251                     else
252                     {
253                         return createMessageWithJmsMessagePayload(result);
254                     }
255                 }
256                 else
257                 {
258                     int timeout = event.getTimeout();
259 
260                     if (logger.isDebugEnabled())
261                     {
262                         logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
263                     }
264 
265                     Message result = consumer.receive(timeout);
266                     if (result == null)
267                     {
268                         logger.debug("No message was returned via replyTo destination " + replyTo);
269                         return createNullMuleMessage();
270                     }
271                     else
272                     {
273                         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                 return returnOriginalMessageAsReply ? createMuleMessage(msg) : null;
283             }
284         }
285         finally
286         {
287             connector.closeQuietly(producer);
288             connector.closeQuietly(consumer);
289 
290             // TODO AP check if TopicResolver is to be utilized for temp destinations as well
291             if (replyTo != null && (replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic))
292             {
293                 if (replyTo instanceof TemporaryQueue)
294                 {
295                     connector.closeQuietly((TemporaryQueue) replyTo);
296                 }
297                 else
298                 {
299                     // hope there are no more non-standard tricks from JMS vendors
300                     // here ;)
301                     connector.closeQuietly((TemporaryTopic) replyTo);
302                 }
303             }
304 
305             if (!sessionManaged && transacted) { 
306                 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             if (session != null && !cached && !transacted)
313             {
314                 connector.closeQuietly(session);
315             }
316         }
317     }
318 
319     protected MuleMessage createMessageWithJmsMessagePayload(Message jmsMessage) throws Exception
320     {
321         MuleMessage muleMessage = createMuleMessage(jmsMessage);
322         Object payload = JmsMessageUtils.toObject(jmsMessage, connector.getSpecification(),
323             endpoint.getEncoding());
324         muleMessage.setPayload(payload);
325         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     }
339     
340     protected void handleMultiTx(Session session) throws Exception
341     {
342         logger.debug("Multi-transaction support is not available in Mule Community Edition.");
343     }
344 
345     @Override
346     protected MuleMessage doSend(MuleEvent event) throws Exception
347     {
348         return dispatchMessage(event, true);
349     }
350 
351     @Override
352     protected void doDispose()
353     {
354         // template method
355     }
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     }
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         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         String selector = null;
389         //Only used by topics
390         String durableName;
391         //If we're not using
392         if (!(replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic))
393         {
394             String jmsCorrelationId = currentMessage.getJMSCorrelationID();
395             if (jmsCorrelationId == null)
396             {
397                 jmsCorrelationId = currentMessage.getJMSMessageID();
398             }
399 
400             selector = "JMSCorrelationID='" + jmsCorrelationId + "'";
401             if (logger.isDebugEnabled())
402             {
403                 logger.debug("ReplyTo Selector is: " + selector);
404             }
405         }
406 
407         //We need to set the durableName and Selector if using topics
408         if (topic)
409         {
410             String tempDurable = (String) event.getEndpoint().getProperties().get(JmsConstants.DURABLE_PROPERTY);
411             boolean durable = connector.isDurable();
412             if (tempDurable != null)
413             {
414                 durable = Boolean.valueOf(tempDurable);
415             }
416             // Get the durable subscriber name if there is one
417             durableName = (String) event.getEndpoint().getProperties().get(
418                     JmsConstants.DURABLE_NAME_PROPERTY);
419             if (durableName == null && durable && topic)
420             {
421                 durableName = "mule." + connector.getName() + "." + event.getEndpoint().getEndpointURI().getAddress();
422                 if (logger.isDebugEnabled())
423                 {
424                     logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: " +
425                                  durableName);
426                 }
427             }
428         }
429         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         Destination replyTo = null;
436 
437         // Some JMS implementations might not support the ReplyTo property.
438         if (isHandleReplyTo(message, event))
439         {
440 
441             Object tempReplyTo = event.getMessage().getOutboundProperty(JmsConstants.JMS_REPLY_TO);
442             if (tempReplyTo == null)
443             {
444                 //It may be a Mule URI or global endpoint Ref
445                 tempReplyTo = event.getMessage().getOutboundProperty(MuleProperties.MULE_REPLY_TO_PROPERTY);
446                 if (tempReplyTo != null)
447                 {
448                     int i = tempReplyTo.toString().indexOf("://");
449                     if (i > -1)
450                     {
451                         tempReplyTo = tempReplyTo.toString().substring(i+3);
452                     }
453                     else
454                     {
455                         EndpointBuilder epb = event.getMuleContext().getRegistry().lookupEndpointBuilder(tempReplyTo.toString());
456                         if (epb != null)
457                         {
458                             tempReplyTo = epb.buildOutboundEndpoint().getEndpointURI().getAddress();
459                         }
460                     }
461                 }
462             }
463             if (tempReplyTo != null)
464             {
465                 if (tempReplyTo instanceof Destination)
466                 {
467                     replyTo = (Destination) tempReplyTo;
468                 }
469                 else
470                 {
471                     // TODO AP should this drill-down be moved into the resolver as well?
472                     boolean replyToTopic = false;
473                     String reply = tempReplyTo.toString();
474                     int i = reply.indexOf(":");
475                     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                         String qtype = reply.substring(0, i);
482                         replyToTopic = JmsConstants.TOPIC_PROPERTY.equalsIgnoreCase(qtype);
483                         reply = reply.substring(i + 1);
484                     }
485                     replyTo = connector.getJmsSupport().createDestination(session, reply, replyToTopic, endpoint);
486                 }
487             }
488             // Are we going to wait for a return event ?
489             if (remoteSync && replyTo == null && !disableTemporaryDestinations)
490             {
491                 replyTo = connector.getJmsSupport().createTemporaryDestination(session, topic);
492             }
493         }
494         return replyTo;
495 
496     }
497 
498     protected class ReplyToListener implements MessageListener
499     {
500         private final Latch latch;
501         private volatile Message message;
502         private final WaitableBoolean released = new WaitableBoolean(false);
503 
504         public ReplyToListener(Latch latch)
505         {
506             this.latch = latch;
507         }
508 
509         public Message getMessage()
510         {
511             return message;
512         }
513 
514         public void release()
515         {
516             released.set(true);
517         }
518 
519         public void onMessage(Message message)
520         {
521             this.message = message;
522             latch.countDown();
523             try
524             {
525                 released.whenTrue(null);
526             }
527             catch (InterruptedException e)
528             {
529                 // ignored
530             }
531         }
532     }
533     
534     @Override
535     protected void applyOutboundTransformers(MuleEvent event) throws MuleException
536     {
537         try
538         {
539             preTransformMessage(event.getMessage());
540         }
541         catch (Exception e)
542         {
543             throw new TransformerException(CoreMessages.failedToInvoke("preTransformMessage"), e);
544         }
545         super.applyOutboundTransformers(event);
546     }
547 
548 }