View Javadoc

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          super(endpoint);
60          this.connector = (JmsConnector) endpoint.getConnector();
61      }
62  
63      protected void doDispatch(UMOEvent event) throws Exception
64      {
65          dispatchMessage(event);
66      }
67  
68      protected void doConnect() throws Exception
69      {
70          // template method
71      }
72  
73      protected void doDisconnect() throws Exception
74      {
75          // template method
76      }
77  
78      private UMOMessage dispatchMessage(UMOEvent event) throws Exception
79      {
80          Session session = null;
81          MessageProducer producer = null;
82          MessageConsumer consumer = null;
83          Destination replyTo = null;
84          boolean transacted = false;
85          boolean cached = false;
86          boolean remoteSync = useRemoteSync(event);
87  
88          if (logger.isDebugEnabled())
89          {
90              logger.debug("dispatching on endpoint: " + event.getEndpoint().getEndpointURI()
91                           + ". Event id is: " + event.getId());
92          }
93  
94          try
95          {
96              session = connector.getSessionFromTransaction();
97              if (session != null)
98              {
99                  transacted = true;
100 
101                 // If a transaction is running, we can not receive any messages
102                 // in the same transaction.
103                 if (remoteSync)
104                 {
105                     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             else if (event.getMessage().getBooleanProperty(JmsConstants.CACHE_JMS_SESSIONS_PROPERTY,
112                 connector.isCacheJmsSessions()))
113             {
114                 cached = true;
115                 if (cachedSession != null)
116                 {
117                     session = cachedSession;
118                 }
119                 else
120                 {
121                     session = connector.getSession(event.getEndpoint());
122                     cachedSession = session;
123                 }
124             }
125             else
126             {
127                 session = connector.getSession(event.getEndpoint());
128                 if (event.getEndpoint().getTransactionConfig().isTransacted())
129                 {
130                     transacted = true;
131                 }
132             }
133 
134             UMOEndpointURI endpointUri = event.getEndpoint().getEndpointURI();
135 
136             boolean topic = connector.getTopicResolver().isTopic(event.getEndpoint(), true);
137 
138             Destination dest = connector.getJmsSupport().createDestination(session, endpointUri.getAddress(),
139                 topic);
140             producer = connector.getJmsSupport().createProducer(session, dest, topic);
141 
142             Object message = event.getTransformedMessage();
143             if (!(message instanceof Message))
144             {
145                 throw new DispatchException(
146                     JmsMessages.checkTransformer("JMS message", message.getClass(), connector.getName()),
147                     event.getMessage(), event.getEndpoint());
148             }
149 
150             Message msg = (Message) message;
151             if (event.getMessage().getCorrelationId() != null)
152             {
153                 msg.setJMSCorrelationID(event.getMessage().getCorrelationId());
154             }
155 
156             UMOMessage eventMsg = event.getMessage();
157 
158             // Some JMS implementations might not support the ReplyTo property.
159             if (connector.supportsProperty(JmsConstants.JMS_REPLY_TO))
160             {
161                 Object tempReplyTo = eventMsg.removeProperty(JmsConstants.JMS_REPLY_TO);
162                 if (tempReplyTo != null)
163                 {
164                     if (tempReplyTo instanceof Destination)
165                     {
166                         replyTo = (Destination) tempReplyTo;
167                     }
168                     else
169                     {
170                         // TODO AP should this drill-down be moved into the resolver as well?
171                         boolean replyToTopic = false;
172                         String reply = tempReplyTo.toString();
173                         int i = reply.indexOf(":");
174                         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                             String qtype = reply.substring(0, i);
181                             replyToTopic = JmsConstants.TOPIC_PROPERTY.equalsIgnoreCase(qtype);
182                             reply = reply.substring(i + 1);
183                         }
184                         replyTo = connector.getJmsSupport().createDestination(session, reply, replyToTopic);
185                     }
186                 }
187                 // Are we going to wait for a return event ?
188                 if (remoteSync && replyTo == null)
189                 {
190                     replyTo = connector.getJmsSupport().createTemporaryDestination(session, topic);
191                 }
192                 // Set the replyTo property
193                 if (replyTo != null)
194                 {
195                     msg.setJMSReplyTo(replyTo);
196                 }
197 
198                 // Are we going to wait for a return event ?
199                 if (remoteSync)
200                 {
201                     consumer = connector.getJmsSupport().createConsumer(session, replyTo, topic);
202                 }
203             }
204 
205             // QoS support
206             String ttlString = (String) eventMsg.removeProperty(JmsConstants.TIME_TO_LIVE_PROPERTY);
207             String priorityString = (String) eventMsg.removeProperty(JmsConstants.PRIORITY_PROPERTY);
208             String persistentDeliveryString = (String) eventMsg.removeProperty(JmsConstants.PERSISTENT_DELIVERY_PROPERTY);
209 
210             long ttl = StringUtils.isNotBlank(ttlString)
211                                 ? NumberUtils.toLong(ttlString)
212                                 : Message.DEFAULT_TIME_TO_LIVE;
213             int priority = StringUtils.isNotBlank(priorityString)
214                                 ? NumberUtils.toInt(priorityString)
215                                 : Message.DEFAULT_PRIORITY;
216             boolean persistent = StringUtils.isNotBlank(persistentDeliveryString)
217                                 ? BooleanUtils.toBoolean(persistentDeliveryString)
218                                 : connector.isPersistentDelivery();
219 
220             if (connector.isHonorQosHeaders())
221             {
222                 int priorityProp = eventMsg.getIntProperty(JmsConstants.JMS_PRIORITY, UMOConnector.INT_VALUE_NOT_SET);
223                 int deliveryModeProp = eventMsg.getIntProperty(JmsConstants.JMS_DELIVERY_MODE, UMOConnector.INT_VALUE_NOT_SET);
224 
225                 if (priorityProp != UMOConnector.INT_VALUE_NOT_SET)
226                 {
227                     priority = priorityProp;
228                 }
229                 if (deliveryModeProp != UMOConnector.INT_VALUE_NOT_SET)
230                 {
231                     persistent = deliveryModeProp == DeliveryMode.PERSISTENT;
232                 }
233             }
234 
235             if (logger.isDebugEnabled())
236             {
237                 logger.debug("Sending message of type " + ClassUtils.getSimpleName(msg.getClass()));
238             }
239 
240             if (consumer != null && topic)
241             {
242                 // need to register a listener for a topic
243                 Latch l = new Latch();
244                 ReplyToListener listener = new ReplyToListener(l);
245                 consumer.setMessageListener(listener);
246 
247                 connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic);
248 
249                 int timeout = event.getTimeout();
250 
251                 if (logger.isDebugEnabled())
252                 {
253                     logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
254                 }
255 
256                 l.await(timeout, TimeUnit.MILLISECONDS);
257                 consumer.setMessageListener(null);
258                 listener.release();
259                 Message result = listener.getMessage();
260                 if (result == null)
261                 {
262                     logger.debug("No message was returned via replyTo destination");
263                     return null;
264                 }
265                 else
266                 {
267                     UMOMessageAdapter adapter = connector.getMessageAdapter(result);
268                     return new MuleMessage(JmsMessageUtils.toObject(result, connector.getSpecification()),
269                         adapter);
270                 }
271             }
272             else
273             {
274                 connector.getJmsSupport().send(producer, msg, persistent, priority, ttl, topic);
275                 if (consumer != null)
276                 {
277                     int timeout = event.getTimeout();
278 
279                     if (logger.isDebugEnabled())
280                     {
281                         logger.debug("Waiting for return event for: " + timeout + " ms on " + replyTo);
282                     }
283 
284                     Message result = consumer.receive(timeout);
285                     if (result == null)
286                     {
287                         logger.debug("No message was returned via replyTo destination");
288                         return null;
289                     }
290                     else
291                     {
292                         UMOMessageAdapter adapter = connector.getMessageAdapter(result);
293                         return new MuleMessage(
294                             JmsMessageUtils.toObject(result, connector.getSpecification()), adapter);
295                     }
296                 }
297             }
298             return null;
299         }
300         finally
301         {
302             connector.closeQuietly(producer);
303             connector.closeQuietly(consumer);
304 
305             // TODO AP check if TopicResolver is to be utilized for temp destinations as well
306             if (replyTo != null && (replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic))
307             {
308                 if (replyTo instanceof TemporaryQueue)
309                 {
310                     connector.closeQuietly((TemporaryQueue) replyTo);
311                 }
312                 else
313                 {
314                     // hope there are no more non-standard tricks from JMS vendors
315                     // here ;)
316                     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             if (session != null && !cached && !transacted)
323             {
324                 connector.closeQuietly(session);
325             }
326         }
327     }
328 
329     protected UMOMessage doSend(UMOEvent event) throws Exception
330     {
331         UMOMessage message = dispatchMessage(event);
332         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         Session session = null;
349         MessageConsumer consumer = null;
350 
351         try
352         {
353             final boolean topic = connector.getTopicResolver().isTopic(endpoint);
354 
355             JmsSupport support = connector.getJmsSupport();
356             session = connector.getSession(false, topic);
357             Destination dest = support.createDestination(session, endpoint.getEndpointURI().getAddress(),
358                 topic);
359 
360             // Extract jms selector
361             String selector = null;
362             if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
363             {
364                 selector = ((JmsSelectorFilter) endpoint.getFilter()).getExpression();
365             }
366             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                 selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
371             }
372             String tempDurable = (String) endpoint.getProperties().get(JmsConstants.DURABLE_PROPERTY);
373             boolean durable = connector.isDurable();
374             if (tempDurable != null)
375             {
376                 durable = Boolean.valueOf(tempDurable).booleanValue();
377             }
378 
379             // Get the durable subscriber name if there is one
380             String durableName = (String) endpoint.getProperties().get(JmsConstants.DURABLE_NAME_PROPERTY);
381             if (durableName == null && durable && topic)
382             {
383                 durableName = "mule." + connector.getName() + "." + endpoint.getEndpointURI().getAddress();
384                 if (logger.isDebugEnabled())
385                 {
386                     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             consumer = support.createConsumer(session, dest, selector, connector.isNoLocal(), durableName,
393                 topic);
394 
395             try
396             {
397                 Message message;
398 
399                 if (timeout == RECEIVE_NO_WAIT)
400                 {
401                     message = consumer.receiveNoWait();
402                 }
403                 else if (timeout == RECEIVE_WAIT_INDEFINITELY)
404                 {
405                     message = consumer.receive();
406                 }
407                 else
408                 {
409                     message = consumer.receive(timeout);
410                 }
411 
412                 if (message == null)
413                 {
414                     return null;
415                 }
416 
417                 message = connector.preProcessMessage(message, session);
418 
419                 return new MuleMessage(connector.getMessageAdapter(message));
420             }
421             catch (Exception e)
422             {
423                 connector.handleException(e);
424                 return null;
425             }
426         }
427         finally
428         {
429             connector.closeQuietly(consumer);
430             connector.closeQuietly(session);
431         }
432     }
433 
434     protected void doDispose()
435     {
436         // template method
437     }
438 
439     private class ReplyToListener implements MessageListener
440     {
441         private final Latch latch;
442         private volatile Message message;
443         private final WaitableBoolean released = new WaitableBoolean(false);
444 
445         public ReplyToListener(Latch latch)
446         {
447             this.latch = latch;
448         }
449 
450         public Message getMessage()
451         {
452             return message;
453         }
454 
455         public void release()
456         {
457             released.set(true);
458         }
459 
460         public void onMessage(Message message)
461         {
462             this.message = message;
463             latch.countDown();
464             try
465             {
466                 released.whenTrue(null);
467             }
468             catch (InterruptedException e)
469             {
470                 // ignored
471             }
472         }
473     }
474 
475 }