View Javadoc

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