View Javadoc

1   /*
2    * $Id: XaTransactedJmsMessageReceiver.java 19191 2010-08-25 21:05:23Z tcarlson $
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.util.ClassUtils;
28  import org.mule.util.MapUtils;
29  
30  import java.util.List;
31  
32  import javax.jms.Destination;
33  import javax.jms.JMSException;
34  import javax.jms.Message;
35  import javax.jms.MessageConsumer;
36  import javax.jms.Session;
37  
38  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
39  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicReference;
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();
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
66      {
67          public JmsThreadContext getContext()
68          {
69              return (JmsThreadContext)get();
70          }
71  
72          @Override
73          protected Object 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) 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             public Void doInTransaction() throws Exception
167             {
168                 try
169                 {
170                     List messages = getMessages();
171                     if (messages != null && messages.size() > 0)
172                     {
173                         for (Object message : messages)
174                         {
175                             processMessage(message);
176                         }
177                     }
178                     return null;
179                 }
180                 catch (Exception e)
181                 {
182                     // There is not a need to close resources here,
183                     // they will be close by XaTransaction,  
184                     JmsThreadContext ctx = context.getContext();
185                     ctx.consumer = null;
186                     Transaction tx = TransactionCoordination.getInstance().getTransaction();
187                     if (ctx.session != null && tx instanceof XaTransaction.MuleXaObject)
188                     {
189                         if (ctx.session instanceof XaTransaction.MuleXaObject)
190                         {
191                             ((XaTransaction.MuleXaObject) ctx.session).setReuseObject(false);
192                         }
193                         else
194                         {
195                             logger.warn("Session should be XA, but is of type " + ctx.session.getClass().getName());
196                         }
197                     }
198                     ctx.session = null;
199                     throw e;
200                 }
201             }
202         };
203         
204         tt.execute(cb);
205     }
206 
207     @Override
208     protected List<MuleMessage> getMessages() throws Exception
209     {
210         Session session = this.connector.getSessionFromTransaction();
211         Transaction tx = TransactionCoordination.getInstance().getTransaction();
212         MessageConsumer consumer = createConsumer();
213 
214         // Retrieve message
215         Message message = null;
216         try
217         {
218             message = consumer.receive(timeout);
219         }
220         catch (JMSException e)
221         {
222             // If we're being disconnected, ignore the exception
223             if (!this.isConnected())
224             {
225                 // ignore
226             }
227             else
228             {
229                 throw e;
230             }
231         }
232         
233         if (message == null)
234         {
235             if (tx != null)
236             {
237                 tx.setRollbackOnly();
238             }
239             return null;
240         }
241         message = connector.preProcessMessage(message, session);
242 
243         // Process message
244         if (logger.isDebugEnabled())
245         {
246             logger.debug("Message received it is of type: " +
247                     ClassUtils.getSimpleName(message.getClass()));
248             if (message.getJMSDestination() != null)
249             {
250                 logger.debug("Message received on " + message.getJMSDestination() + " ("
251                              + message.getJMSDestination().getClass().getName() + ")");
252             }
253             else
254             {
255                 logger.debug("Message received on unknown destination");
256             }
257             logger.debug("Message CorrelationId is: " + message.getJMSCorrelationID());
258             logger.debug("Jms Message Id is: " + message.getJMSMessageID());
259         }
260 
261         if (message.getJMSRedelivered())
262         {
263             if (logger.isDebugEnabled())
264             {
265                 logger.debug("Message with correlationId: " + message.getJMSCorrelationID()
266                              + " is redelivered. handing off to Exception Handler");
267             }
268             ((RedeliveryHandler) redeliveryHandler.get()).handleRedelivery(message);
269         }
270 
271         MuleMessage messageToRoute = createMuleMessage(message, endpoint.getEncoding());
272         routeMessage(messageToRoute);
273         return null;
274     }
275 
276     @Override
277     protected void processMessage(Object msg) throws Exception
278     {
279         // This method is never called as the
280         // message is processed when received
281     }
282 
283     /**
284      * Close Sesison and consumer
285      */
286     protected void closeResource(boolean force)
287     {
288         JmsThreadContext ctx = context.getContext();
289         if (ctx == null)
290         {
291             return;
292         }
293         
294         // Close consumer
295         if (force || !reuseSession || !reuseConsumer)
296         {
297             connector.closeQuietly(ctx.consumer);
298             ctx.consumer = null;
299         }
300             
301         // Do not close session if a transaction is in progress
302         // the session will be closed by the transaction
303         if (force || !reuseSession)
304         {
305             connector.closeQuietly(ctx.session);
306             ctx.session = null;
307         }        
308     }
309 
310     /**
311      * Create a consumer for the jms destination
312      * 
313      * @throws Exception
314      */
315     protected MessageConsumer createConsumer() throws Exception
316     {
317         logger.debug("Create a consumer for the jms destination");
318         try
319         {
320             JmsSupport jmsSupport = this.connector.getJmsSupport();
321 
322             JmsThreadContext ctx = context.getContext();
323             if (ctx == null)
324             {
325                 ctx = new JmsThreadContext();
326             }
327             
328             Session session;
329             Transaction tx = TransactionCoordination.getInstance().getTransaction();
330             if (this.reuseSession && ctx.session != null)
331             {
332                 session = ctx.session;
333                 tx.bindResource(this.connector.getConnection(), session);
334             }
335             else
336             {
337                 session = this.connector.getSession(endpoint);
338                 if (session != null && tx != null)
339                 {
340                     if (session instanceof XaTransaction.MuleXaObject)
341                     {
342                         ((XaTransaction.MuleXaObject) session).setReuseObject(reuseSession);
343                     }
344                     else
345                     {
346                         logger.warn("Session should be XA, but is of type " + session.getClass().getName());
347                     }
348                 }
349             }
350             
351             if (reuseSession)
352             {
353                 ctx.session = session;
354             }
355             
356             // TODO How can I verify that the consumer is active?
357             if (this.reuseConsumer && ctx.consumer != null)
358             {
359                 return ctx.consumer;
360             }
361 
362             // Create destination
363             final boolean topic = connector.getTopicResolver().isTopic(endpoint);
364             Destination dest = jmsSupport.createDestination(session, endpoint);
365 
366             // Extract jms selector
367             String selector = null;
368             if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
369             {
370                 selector = ((JmsSelectorFilter)endpoint.getFilter()).getExpression();
371             }
372             else if (endpoint.getProperties() != null)
373             {
374                 // still allow the selector to be set as a property on the endpoint
375                 // to be backward compatible
376                 selector = (String)endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
377             }
378             String tempDurable = (String)endpoint.getProperties().get("durable");
379             boolean durable = connector.isDurable();
380             if (tempDurable != null)
381             {
382                 durable = Boolean.valueOf(tempDurable);
383             }
384 
385             // Get the durable subscriber name if there is one
386             String durableName = (String)endpoint.getProperties().get("durableName");
387             if (durableName == null && durable && topic)
388             {
389                 durableName = "mule." + connector.getName() + "." + endpoint.getEndpointURI().getAddress();
390                 logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
391                              + durableName);
392             }
393 
394             // Create consumer
395             MessageConsumer consumer = jmsSupport.createConsumer(session, dest, selector, connector.isNoLocal(),
396                 durableName, topic, endpoint);
397             if (reuseConsumer)
398             {
399                 ctx.consumer = consumer;
400             }
401             return consumer;
402         }
403         catch (JMSException e)
404         {
405             throw new ConnectException(e, this);
406         }
407     }
408 }