View Javadoc

1   /*
2    * $Id: XaTransactedJmsMessageReceiver.java 10497 2008-01-24 10:10:32Z 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.ConnectException;
15  import org.mule.providers.SingleAttemptConnectionStrategy;
16  import org.mule.providers.TransactedPollingMessageReceiver;
17  import org.mule.providers.jms.filters.JmsSelectorFilter;
18  import org.mule.transaction.TransactionCoordination;
19  import org.mule.transaction.XaTransaction;
20  import org.mule.umo.UMOComponent;
21  import org.mule.umo.UMOTransaction;
22  import org.mule.umo.endpoint.UMOEndpoint;
23  import org.mule.umo.lifecycle.InitialisationException;
24  import org.mule.umo.provider.UMOConnector;
25  import org.mule.umo.provider.UMOMessageAdapter;
26  import org.mule.util.ClassUtils;
27  import org.mule.util.MapUtils;
28  
29  import java.util.List;
30  
31  import javax.jms.Destination;
32  import javax.jms.JMSException;
33  import javax.jms.Message;
34  import javax.jms.MessageConsumer;
35  import javax.jms.Session;
36  
37  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
38  
39  public class XaTransactedJmsMessageReceiver extends TransactedPollingMessageReceiver
40  {
41      public static final long DEFAULT_JMS_POLL_FREQUENCY = 100;
42      public static final TimeUnit DEFAULT_JMS_POLL_TIMEUNIT = TimeUnit.MILLISECONDS;
43  
44      protected final JmsConnector connector;
45      protected boolean reuseConsumer;
46      protected boolean reuseSession;
47      protected final ThreadContextLocal context = new ThreadContextLocal();
48      protected final long timeout;
49      protected final RedeliveryHandler redeliveryHandler;
50  
51      /**
52       * Holder receiving the session and consumer for this thread.
53       */
54      protected static class JmsThreadContext
55      {
56          public Session session;
57          public MessageConsumer consumer;
58      }
59  
60      /**
61       * Strongly typed ThreadLocal for ThreadContext.
62       */
63      protected static class ThreadContextLocal extends ThreadLocal
64      {
65          public JmsThreadContext getContext()
66          {
67              return (JmsThreadContext) get();
68          }
69  
70          protected Object initialValue()
71          {
72              return new JmsThreadContext();
73          }
74      }
75  
76      public XaTransactedJmsMessageReceiver(UMOConnector umoConnector, UMOComponent component, UMOEndpoint endpoint)
77              throws InitialisationException
78      {
79          super(umoConnector, component, endpoint);
80          // TODO AP: find appropriate value for polling frequency with the scheduler;
81          // see setFrequency/setTimeUnit & VMMessageReceiver for more
82          this.setFrequency(DEFAULT_JMS_POLL_FREQUENCY);
83          this.setTimeUnit(DEFAULT_JMS_POLL_TIMEUNIT);
84          this.connector = (JmsConnector) umoConnector;
85          this.timeout = endpoint.getTransactionConfig().getTimeout();
86  
87          // If reconnection is set, default reuse strategy to false
88          // as some jms brokers will not detect lost connections if the
89          // same consumer / session is used
90          if (this.connectionStrategy instanceof SingleAttemptConnectionStrategy)
91          {
92              this.reuseConsumer = true;
93              this.reuseSession = true;
94          }
95  
96          // User may override reuse strategy if necessary
97          this.reuseConsumer = MapUtils.getBooleanValue(endpoint.getProperties(), "reuseConsumer",
98                                                        this.reuseConsumer);
99          this.reuseSession = MapUtils.getBooleanValue(endpoint.getProperties(), "reuseSession",
100                                                      this.reuseSession);
101 
102         // Do extra validation, XA Topic & reuse are incompatible. See MULE-2622
103         boolean topic = connector.getTopicResolver().isTopic(getEndpoint());
104         if (topic && (reuseConsumer || reuseSession))
105         {
106             logger.warn("Destination " + getEndpoint().getEndpointURI() + " is a topic and XA transaction was " +
107                         "configured. Forcing 'reuseSession' and 'reuseConsumer' to false. Set these " +
108                         "on endpoint to avoid the message.");
109             reuseConsumer = false;
110             reuseSession = false;
111         }
112 
113         // Check if the destination is a queue and
114         // if we are in transactional mode.
115         // If true, set receiveMessagesInTransaction to true.
116         // It will start multiple threads, depending on the threading profile.
117 
118         // If we're using topics we don't want to use multiple receivers as we'll get
119         // the same message multiple times
120         this.setUseMultipleTransactedReceivers(!topic);
121 
122         try
123         {
124             redeliveryHandler = this.connector.createRedeliveryHandler();
125             redeliveryHandler.setConnector(this.connector);
126         }
127         catch (Exception e)
128         {
129             throw new InitialisationException(e, this);
130         }
131 
132     }
133 
134     protected void doDispose()
135     {
136         // template method
137     }
138 
139     protected void doConnect() throws Exception
140     {
141         if (connector.isConnected() && connector.isEagerConsumer())
142         {
143             createConsumer();
144             // creating this consumer now would prevent from the actual worker
145             // consumer
146             // to receive the message!
147             //Antoine Borg 08 Dec 2006 - Uncommented for MULE-1150
148             // if we comment this line, if one tries to restart the service through
149             // JMX,
150             // this will fail...
151             //This Line seems to be the root to a number of problems and differences between
152             //Jms providers. A which point the consumer is created changes how the conneciton can be managed.
153             //For example, WebsphereMQ needs the consumer created here, otherwise ReconnectionStrategies don't work properly
154             //(See MULE-1150) However, is the consumer is created here for Active MQ, The worker thread cannot actually
155             //receive the message.  We need to test with a few more Jms providers and transactions to see which behaviour
156             // is correct.  My gut feeling is that the consumer should be created here and there is a bug in ActiveMQ
157         }
158     }
159 
160     protected void doDisconnect() throws Exception
161     {
162         if (connector.isConnected())
163         {
164             closeConsumer(true);
165         }
166     }
167 
168     /**
169      * The poll method is overriden from the {@link TransactedPollingMessageReceiver}
170      */
171     public void poll() throws Exception
172     {
173         try
174         {
175             JmsThreadContext ctx = context.getContext();
176             // Create consumer if necessary
177             if (ctx.consumer == null)
178             {
179                 createConsumer();
180             }
181             // Do polling
182             super.poll();
183         }
184         catch (Exception e)
185         {
186             // Force consumer to close
187             closeConsumer(true);
188             throw e;
189         }
190         finally
191         {
192             // Close consumer if necessary
193             closeConsumer(false);
194         }
195     }
196 
197     /*
198      * (non-Javadoc)
199      * 
200      * @see org.mule.providers.TransactionEnabledPollingMessageReceiver#getMessages()
201      */
202     protected List getMessages() throws Exception
203     {
204         // As the session is created outside the transaction, it is not
205         // bound to it yet
206         JmsThreadContext ctx = context.getContext();
207 
208         UMOTransaction tx = TransactionCoordination.getInstance().getTransaction();
209         if (tx != null)
210         {
211             tx.bindResource(connector.getConnection(), ctx.session);
212         }
213 
214         // Retrieve message
215         Message message = null;
216         try
217         {
218             message = ctx.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         if (message == null)
233         {
234             if (tx != null)
235             {
236                 tx.setRollbackOnly();
237             }
238             return null;
239         }
240         message = connector.preProcessMessage(message, ctx.session);
241 
242         // Process message
243         if (logger.isDebugEnabled())
244         {
245             logger.debug("Message received it is of type: " +
246                          ClassUtils.getSimpleName(message.getClass()));
247             if (message.getJMSDestination() != null)
248             {
249                 logger.debug("Message received on " + message.getJMSDestination() + " ("
250                              + message.getJMSDestination().getClass().getName() + ")");
251             }
252             else
253             {
254                 logger.debug("Message received on unknown destination");
255             }
256             logger.debug("Message CorrelationId is: " + message.getJMSCorrelationID());
257             logger.debug("Jms Message Id is: " + message.getJMSMessageID());
258         }
259 
260         if (message.getJMSRedelivered())
261         {
262             if (logger.isDebugEnabled())
263             {
264                 logger.debug("Message with correlationId: " + message.getJMSCorrelationID()
265                              + " is redelivered. handing off to Exception Handler");
266             }
267             redeliveryHandler.handleRedelivery(message);
268         }
269 
270         if (tx instanceof JmsClientAcknowledgeTransaction)
271         {
272             tx.bindResource(message, null);
273         }
274 
275         UMOMessageAdapter adapter = connector.getMessageAdapter(message);
276         routeMessage(new MuleMessage(adapter));
277         return null;
278     }
279 
280     /*
281      * (non-Javadoc)
282      * 
283      * @see org.mule.providers.TransactionEnabledPollingMessageReceiver#processMessage(java.lang.Object)
284      */
285     protected void processMessage(Object msg) throws Exception
286     {
287         // This method is never called as the
288         // message is processed when received
289     }
290 
291     protected void closeConsumer(boolean force)
292     {
293         JmsThreadContext ctx = context.getContext();
294         if (ctx == null)
295         {
296             return;
297         }
298         // Close consumer
299         if (force || !reuseSession || !reuseConsumer)
300         {
301             connector.closeQuietly(ctx.consumer);
302             ctx.consumer = null;
303         }
304         // Do not close session if a transaction is in progress
305         // the session will be closed by the transaction
306         if (force || !reuseSession)
307         {
308             connector.closeQuietly(ctx.session);
309             ctx.session = null;
310         }
311     }
312 
313     /**
314      * Create a consumer for the jms destination
315      *
316      * @throws Exception
317      */
318     protected void createConsumer() throws Exception
319     {
320         try
321         {
322             JmsSupport jmsSupport = this.connector.getJmsSupport();
323             JmsThreadContext ctx = context.getContext();
324             // Create session if none exists
325             if (ctx.session == null)
326             {
327                 ctx.session = this.connector.getSession(endpoint);
328                 //set reuse flag
329                 ((XaTransaction.MuleXaObject) ctx.session).setReuseObject(reuseSession);
330 
331             }
332 
333             // Create destination
334             final boolean topic = connector.getTopicResolver().isTopic(endpoint);
335             Destination dest = jmsSupport.createDestination(ctx.session, endpoint.getEndpointURI()
336                     .getAddress(), topic);
337 
338             // Extract jms selector
339             String selector = null;
340             if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
341             {
342                 selector = ((JmsSelectorFilter) endpoint.getFilter()).getExpression();
343             }
344             else if (endpoint.getProperties() != null)
345             {
346                 // still allow the selector to be set as a property on the endpoint
347                 // to be backward compatible
348                 selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
349             }
350             String tempDurable = (String) endpoint.getProperties().get("durable");
351             boolean durable = connector.isDurable();
352             if (tempDurable != null)
353             {
354                 durable = Boolean.valueOf(tempDurable).booleanValue();
355             }
356 
357             // Get the durable subscriber name if there is one
358             String durableName = (String) endpoint.getProperties().get("durableName");
359             if (durableName == null && durable && topic)
360             {
361                 durableName = "mule." + connector.getName() + "." + endpoint.getEndpointURI().getAddress();
362                 logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
363                              + durableName);
364             }
365 
366             // Create consumer
367             ctx.consumer = jmsSupport.createConsumer(ctx.session, dest, selector, connector.isNoLocal(),
368                                                      durableName, topic);
369         }
370         catch (JMSException e)
371         {
372             throw new ConnectException(e, this);
373         }
374     }
375 }