View Javadoc

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