View Javadoc

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