View Javadoc

1   /*
2    * $Id: XaTransactedJmsMessageReceiver.java 23147 2011-10-11 15:43:38Z pablo.lagreca $
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.MuleMessage;
14  import org.mule.api.construct.FlowConstruct;
15  import org.mule.api.endpoint.InboundEndpoint;
16  import org.mule.api.lifecycle.CreateException;
17  import org.mule.api.transaction.Transaction;
18  import org.mule.api.transaction.TransactionCallback;
19  import org.mule.api.transport.Connector;
20  import org.mule.retry.policies.NoRetryPolicyTemplate;
21  import org.mule.transaction.TransactionCoordination;
22  import org.mule.transaction.TransactionTemplate;
23  import org.mule.transaction.XaTransaction;
24  import org.mule.transport.ConnectException;
25  import org.mule.transport.TransactedPollingMessageReceiver;
26  import org.mule.transport.jms.filters.JmsSelectorFilter;
27  import org.mule.transport.jms.redelivery.RedeliveryHandler;
28  import org.mule.util.ClassUtils;
29  import org.mule.util.MapUtils;
30  
31  import java.util.List;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.atomic.AtomicReference;
34  
35  import javax.jms.Destination;
36  import javax.jms.JMSException;
37  import javax.jms.Message;
38  import javax.jms.MessageConsumer;
39  import javax.jms.Session;
40  
41  public class XaTransactedJmsMessageReceiver extends TransactedPollingMessageReceiver
42  {
43      public static final long DEFAULT_JMS_POLL_FREQUENCY = 100;
44      public static final TimeUnit DEFAULT_JMS_POLL_TIMEUNIT = TimeUnit.MILLISECONDS;
45  
46      protected final JmsConnector connector;
47      protected boolean reuseConsumer;
48      protected boolean reuseSession;
49      protected final ThreadContextLocal context = new ThreadContextLocal();
50      protected final long timeout;
51      private final AtomicReference<RedeliveryHandler> redeliveryHandler = new AtomicReference<RedeliveryHandler>();
52  
53      /**
54       * Holder receiving the session and consumer for this thread.
55       */
56      protected static class JmsThreadContext
57      {
58          public Session session;
59          public MessageConsumer consumer;
60      }
61  
62      /**
63       * Strongly typed ThreadLocal for ThreadContext.
64       */
65      protected static class ThreadContextLocal extends ThreadLocal<JmsThreadContext>
66      {
67          public JmsThreadContext getContext()
68          {
69              return get();
70          }
71  
72          @Override
73          protected JmsThreadContext initialValue()
74          {
75              return new JmsThreadContext();
76          }
77      }
78  
79      public XaTransactedJmsMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
80          throws CreateException
81      {
82          super(connector, flowConstruct, endpoint);
83          // TODO AP: find appropriate value for polling frequency with the scheduler;
84          // see setFrequency/setTimeUnit & VMMessageReceiver for more
85          this.setTimeUnit(DEFAULT_JMS_POLL_TIMEUNIT);
86          this.setFrequency(MapUtils.getLongValue(endpoint.getProperties(), "pollingFrequency", DEFAULT_JMS_POLL_FREQUENCY));
87  
88          this.connector = (JmsConnector) connector;
89          this.timeout = endpoint.getTransactionConfig().getTimeout();
90  
91          // If reconnection is configured, default reuse strategy to false
92          // as some jms brokers will not detect lost connections if the
93          // same consumer / session is used
94          if (retryTemplate != null && !(retryTemplate instanceof NoRetryPolicyTemplate))
95          {
96              this.reuseConsumer = false;
97              this.reuseSession = false;
98          }
99  
100         // User may override reuse strategy if necessary. This is available for legacy reasons,
101         // but this approach is not recommended and should never be set when using XA.
102         this.reuseConsumer = MapUtils.getBooleanValue(endpoint.getProperties(), "reuseConsumer",
103             this.reuseConsumer);
104         this.reuseSession = MapUtils.getBooleanValue(endpoint.getProperties(), "reuseSession",
105             this.reuseSession);
106 
107         // Do extra validation, XA Topic & reuse are incompatible. See MULE-2622
108         boolean topic = this.connector.getTopicResolver().isTopic(getEndpoint());
109         if (topic && (reuseConsumer || reuseSession))
110         {
111             logger.warn("Destination " + getEndpoint().getEndpointURI() + " is a topic and XA transaction was " +
112                         "configured. Forcing 'reuseSession' and 'reuseConsumer' to false. Set these " +
113                         "on endpoint to avoid the message.");
114             reuseConsumer = false;
115             reuseSession = false;
116         }
117 
118         // Check if the destination is a queue and
119         // if we are in transactional mode.
120         // If true, set receiveMessagesInTransaction to true.
121         // It will start multiple threads, depending on the threading profile.
122 
123         // If we're using topics we don't want to use multiple receivers as we'll get
124         // the same message multiple times
125         this.setUseMultipleTransactedReceivers(!topic);
126     }
127 
128     @Override
129     protected void doDispose()
130     {
131         // template method
132     }
133 
134     @Override
135     protected void doConnect() throws Exception
136     {
137         if (redeliveryHandler.compareAndSet(null, connector.getRedeliveryHandlerFactory().create()))
138         {
139             redeliveryHandler.get().setConnector(this.connector);
140         }
141     }
142 
143     @Override
144     protected void doDisconnect() throws Exception
145     {
146         if (connector.isConnected())
147         {
148             // TODO All resources will be close by transaction or by connection close
149             closeResource(true);
150         }
151     }
152 
153     /**
154      * The poll method is overriden from the {@link TransactedPollingMessageReceiver}
155      */
156     @Override
157     public void poll() throws Exception
158     {
159         logger.debug("Polling...");
160 
161         TransactionTemplate<Void> tt = new TransactionTemplate<Void>(
162                                                 endpoint.getTransactionConfig(),
163                                                 connector.getMuleContext());
164         TransactionCallback<Void> cb = new TransactionCallback<Void>()
165         {
166             @Override
167             public Void doInTransaction() throws Exception
168             {
169                 try
170                 {
171                     List messages = getMessages();
172                     if (messages != null && messages.size() > 0)
173                     {
174                         for (Object message : messages)
175                         {
176                             processMessage(message);
177                         }
178                     }
179                     return null;
180                 }
181                 catch (Exception e)
182                 {
183                     // There is not a need to close resources here,
184                     // they will be close by XaTransaction,
185                     JmsThreadContext ctx = context.getContext();
186                     if (ctx.consumer != null)
187                     {
188                         connector.closeQuietly(ctx.consumer);
189                     }
190                     ctx.consumer = null;
191                     Transaction tx = TransactionCoordination.getInstance().getTransaction();
192                     if (ctx.session != null && tx instanceof XaTransaction.MuleXaObject)
193                     {
194                         if (ctx.session instanceof XaTransaction.MuleXaObject)
195                         {
196                             ((XaTransaction.MuleXaObject) ctx.session).setReuseObject(false);
197                         }
198                         else
199                         {
200                             logger.warn("Session should be XA, but is of type " + ctx.session.getClass().getName());
201                         }
202                     }
203                     ctx.session = null;
204                     throw e;
205                 }
206             }
207         };
208 
209         tt.execute(cb);
210     }
211 
212     @Override
213     protected List<MuleMessage> getMessages() throws Exception
214     {
215         Session session = this.connector.getSessionFromTransaction();
216         Transaction tx = TransactionCoordination.getInstance().getTransaction();
217         MessageConsumer consumer = createConsumer();
218 
219         // Retrieve message
220         Message message = null;
221         try
222         {
223             message = consumer.receive(timeout);
224         }
225         catch (JMSException e)
226         {
227             // If we're being disconnected, ignore the exception
228             if (!this.isConnected())
229             {
230                 // ignore
231             }
232             else
233             {
234                 throw e;
235             }
236         }
237 
238         if (message == null)
239         {
240             if (tx != null)
241             {
242                 tx.setRollbackOnly();
243             }
244             return null;
245         }
246         message = connector.preProcessMessage(message, session);
247 
248         // Process message
249         if (logger.isDebugEnabled())
250         {
251             logger.debug("Message received it is of type: " +
252                     ClassUtils.getSimpleName(message.getClass()));
253             if (message.getJMSDestination() != null)
254             {
255                 logger.debug("Message received on " + message.getJMSDestination() + " ("
256                              + message.getJMSDestination().getClass().getName() + ")");
257             }
258             else
259             {
260                 logger.debug("Message received on unknown destination");
261             }
262             logger.debug("Message CorrelationId is: " + message.getJMSCorrelationID());
263             logger.debug("Jms Message Id is: " + message.getJMSMessageID());
264         }
265 
266         if (message.getJMSRedelivered())
267         {
268             if (logger.isDebugEnabled())
269             {
270                 logger.debug("Message with correlationId: " + message.getJMSCorrelationID()
271                              + " is redelivered. handing off to Exception Handler");
272             }
273             redeliveryHandler.get().handleRedelivery(message, (InboundEndpoint) endpoint, flowConstruct);
274         }
275 
276         MuleMessage messageToRoute = createMuleMessage(message, endpoint.getEncoding());
277         routeMessage(messageToRoute);
278         connector.closeQuietly(consumer);
279         return null;
280     }
281 
282     @Override
283     protected void processMessage(Object msg) throws Exception
284     {
285         // This method is never called as the
286         // message is processed when received
287     }
288 
289     /**
290      * Close Sesison and consumer
291      */
292     protected void closeResource(boolean force)
293     {
294         JmsThreadContext ctx = context.getContext();
295         if (ctx == null)
296         {
297             return;
298         }
299 
300         // Close consumer
301         if (force || !reuseSession || !reuseConsumer)
302         {
303             connector.closeQuietly(ctx.consumer);
304             ctx.consumer = null;
305         }
306 
307         // Do not close session if a transaction is in progress
308         // the session will be closed by the transaction
309         if (force || !reuseSession)
310         {
311             connector.closeQuietly(ctx.session);
312             ctx.session = null;
313         }
314     }
315 
316     /**
317      * Create a consumer for the jms destination
318      *
319      * @throws Exception
320      */
321     protected MessageConsumer createConsumer() throws Exception
322     {
323         logger.debug("Create a consumer for the jms destination");
324         try
325         {
326             JmsSupport jmsSupport = this.connector.getJmsSupport();
327 
328             JmsThreadContext ctx = context.getContext();
329             if (ctx == null)
330             {
331                 ctx = new JmsThreadContext();
332             }
333 
334             Session session;
335             Transaction tx = TransactionCoordination.getInstance().getTransaction();
336             if (this.reuseSession && ctx.session != null)
337             {
338                 session = ctx.session;
339                 tx.bindResource(this.connector.getConnection(), session);
340             }
341             else
342             {
343                 session = this.connector.getSession(endpoint);
344                 if (session != null && tx != null)
345                 {
346                     if (session instanceof XaTransaction.MuleXaObject)
347                     {
348                         ((XaTransaction.MuleXaObject) session).setReuseObject(reuseSession);
349                     }
350                     else
351                     {
352                         logger.warn("Session should be XA, but is of type " + session.getClass().getName());
353                     }
354                 }
355             }
356 
357             if (reuseSession)
358             {
359                 ctx.session = session;
360             }
361 
362             // TODO How can I verify that the consumer is active?
363             if (this.reuseConsumer && ctx.consumer != null)
364             {
365                 return ctx.consumer;
366             }
367 
368             // Create destination
369             final boolean topic = connector.getTopicResolver().isTopic(endpoint);
370             Destination dest = jmsSupport.createDestination(session, endpoint);
371 
372             // Extract jms selector
373             String selector = null;
374             JmsSelectorFilter selectorFilter = connector.getSelector(endpoint);
375             if (selectorFilter != null)
376             {
377                 selector = selectorFilter.getExpression();
378             }
379             else if (endpoint.getProperties() != null)
380             {
381                 // still allow the selector to be set as a property on the endpoint
382                 // to be backward compatible
383                 selector = (String)endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
384             }
385             String tempDurable = (String)endpoint.getProperties().get("durable");
386             boolean durable = connector.isDurable();
387             if (tempDurable != null)
388             {
389                 durable = Boolean.valueOf(tempDurable);
390             }
391 
392             // Get the durable subscriber name if there is one
393             String durableName = (String)endpoint.getProperties().get("durableName");
394             if (durableName == null && durable && topic)
395             {
396                 durableName = "mule." + connector.getName() + "." + endpoint.getEndpointURI().getAddress();
397                 logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
398                              + durableName);
399             }
400 
401             // Create consumer
402             MessageConsumer consumer = jmsSupport.createConsumer(session, dest, selector, connector.isNoLocal(),
403                 durableName, topic, endpoint);
404             if (reuseConsumer)
405             {
406                 ctx.consumer = consumer;
407             }
408             return consumer;
409         }
410         catch (JMSException e)
411         {
412             throw new ConnectException(e, this);
413         }
414     }
415 }