View Javadoc

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