View Javadoc

1   /*
2    * $Id: MultiConsumerJmsMessageReceiver.java 23075 2011-10-03 21:51:50Z 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.MessageExchangePattern;
14  import org.mule.api.MessagingException;
15  import org.mule.api.MuleException;
16  import org.mule.api.construct.FlowConstruct;
17  import org.mule.api.endpoint.InboundEndpoint;
18  import org.mule.api.exception.RollbackSourceCallback;
19  import org.mule.api.lifecycle.CreateException;
20  import org.mule.api.lifecycle.LifecycleException;
21  import org.mule.api.transaction.Transaction;
22  import org.mule.api.transaction.TransactionException;
23  import org.mule.api.transport.Connector;
24  import org.mule.api.transport.ReplyToHandler;
25  import org.mule.transaction.TransactionCollection;
26  import org.mule.transport.AbstractMessageReceiver;
27  import org.mule.transport.AbstractReceiverWorker;
28  import org.mule.transport.ConnectException;
29  import org.mule.transport.jms.filters.JmsSelectorFilter;
30  import org.mule.transport.jms.redelivery.RedeliveryHandler;
31  import org.mule.util.ClassUtils;
32  
33  import java.util.ArrayList;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.concurrent.CopyOnWriteArrayList;
37  
38  import javax.jms.Destination;
39  import javax.jms.JMSException;
40  import javax.jms.Message;
41  import javax.jms.MessageConsumer;
42  import javax.jms.MessageListener;
43  import javax.jms.Session;
44  
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  
48  /**
49   * In Mule an endpoint corresponds to a single receiver. It's up to the receiver to do multithreaded consumption and
50   * resource allocation, if needed. This class honors the <code>numberOfConcurrentTransactedReceivers</code> strictly
51   * and will create exactly this number of consumers.
52   */
53  public class MultiConsumerJmsMessageReceiver extends AbstractMessageReceiver
54  {
55      protected final List<SubReceiver> consumers;
56  
57      protected final int receiversCount;
58  
59      private final JmsConnector jmsConnector;
60  
61      public MultiConsumerJmsMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
62              throws CreateException
63      {
64          super(connector, flowConstruct, endpoint);
65  
66          jmsConnector = (JmsConnector) connector;
67  
68          final boolean isTopic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
69          if (isTopic && jmsConnector.getNumberOfConsumers() != 1)
70          {
71              if (logger.isInfoEnabled())
72              {
73                  logger.info("Destination " + getEndpoint().getEndpointURI() + " is a topic, but " + jmsConnector.getNumberOfConsumers() +
74                                  " receivers have been requested. Will configure only 1.");
75              }
76              receiversCount = 1;
77          }
78          else
79          {
80              receiversCount = jmsConnector.getNumberOfConsumers();
81          }
82          if (logger.isDebugEnabled())
83          {
84              logger.debug("Creating " + receiversCount + " sub-receivers for " + endpoint.getEndpointURI());
85          }
86  
87          consumers = new CopyOnWriteArrayList();
88      }
89          
90      @Override
91      protected void doStart() throws MuleException
92      {
93          logger.debug("doStart()");
94          SubReceiver sub;
95          for (Iterator<SubReceiver> it = consumers.iterator(); it.hasNext();)
96          {
97              sub = it.next();
98              sub.doStart();
99          }
100     }
101 
102     @Override
103     protected void doStop() throws MuleException
104     {
105         logger.debug("doStop()");
106         if (consumers != null)
107         {
108             SubReceiver sub;
109             for (Iterator<SubReceiver> it = consumers.iterator(); it.hasNext();)
110             {
111                 sub = it.next();
112                 sub.doStop(true);
113             }
114         }
115     }
116 
117     @Override
118     protected void doConnect() throws Exception
119     {
120         logger.debug("doConnect()");
121 
122         if (!consumers.isEmpty())
123         {
124             throw new IllegalStateException("List should be empty, there may be a concurrency issue here (see EE-1275)");
125         }
126         
127         SubReceiver sub;
128         for (int i = 0; i < receiversCount; i++)
129         {
130             sub = new SubReceiver();
131             sub.doConnect();
132             consumers.add(sub);
133         }
134     }
135 
136     @Override
137     protected void doDisconnect() throws Exception
138     {
139         logger.debug("doDisconnect()");
140 
141         SubReceiver sub;
142         for (Iterator<SubReceiver> it = consumers.iterator(); it.hasNext();)
143         {
144             sub = it.next();
145             try
146             {
147                 sub.doDisconnect();
148             }
149             finally
150             {
151                 sub = null;
152             }
153         }
154         consumers.clear();
155             }
156 
157     @Override
158     protected void doDispose()
159     {
160         logger.debug("doDispose()");
161             }
162 
163     private class SubReceiver implements MessageListener
164     {
165         private final Log subLogger = LogFactory.getLog(getClass());
166 
167         private volatile Session session;
168         private volatile MessageConsumer consumer;
169 
170         protected volatile boolean connected;
171         protected volatile boolean started;
172         
173         protected void doConnect() throws MuleException
174         {
175             subLogger.debug("SUB doConnect()");
176             try
177             {
178                 createConsumer();
179             }
180             catch (Exception e)
181             {
182                 throw new LifecycleException(e, this);
183             }
184             connected = true;
185         }
186 
187         protected void doDisconnect() throws MuleException
188         {
189             subLogger.debug("SUB doDisconnect()");
190             if (started)
191             {
192                 doStop(true);
193             }
194             closeConsumer();
195             connected = false;
196         }
197 
198         protected void closeConsumer()
199         {
200             jmsConnector.closeQuietly(consumer);
201             consumer = null;
202             jmsConnector.closeQuietly(session);
203             session = null;
204         }
205 
206         protected void doStart() throws MuleException
207         {
208             subLogger.debug("SUB doStart()");
209             if (!connected)
210             {
211                 doConnect();
212             }
213             
214             try
215             { 
216                 consumer.setMessageListener(this);
217                 started = true;
218             }
219             catch (JMSException e)
220             {
221                 throw new LifecycleException(e, this);
222             }
223         }
224 
225         /**
226          * Stop the subreceiver.
227          * @param force - if true, any exceptions will be logged but the subreceiver will be considered stopped regardless
228          * @throws MuleException only if force = false
229          */
230         protected void doStop(boolean force) throws MuleException
231         {
232             subLogger.debug("SUB doStop()");
233 
234             if (consumer != null)
235             {
236                 try
237                 {
238                     consumer.setMessageListener(null);
239                     started = false;
240                 }
241                 catch (JMSException e)
242                 {
243                     if (force)
244                     {
245                         logger.warn("Unable to cleanly stop subreceiver: " + e.getMessage());
246                         started = false;
247                     }
248                     else
249                     {
250                         throw new LifecycleException(e, this);
251                     }
252                 }
253             }
254         }
255 
256         /**
257          * Create a consumer for the jms destination.
258          */
259         protected void createConsumer() throws Exception
260         {
261             subLogger.debug("SUB createConsumer()");
262             
263             try
264             {
265                 JmsSupport jmsSupport = jmsConnector.getJmsSupport();
266                 boolean topic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
267 
268                 // Create session if none exists
269                 if (session == null)
270                 {
271                     session = jmsConnector.getSession(endpoint);
272                 }
273 
274                 // Create destination
275                 Destination dest = jmsSupport.createDestination(session, endpoint);
276 
277                 // Extract jms selector
278                 String selector = null;
279                 JmsSelectorFilter selectorFilter = jmsConnector.getSelector(endpoint);
280                 if (selectorFilter != null)
281                 {
282                     selector = selectorFilter.getExpression();
283                 }
284                 else
285                 {
286                     if (endpoint.getProperties() != null)
287                     {
288                         // still allow the selector to be set as a property on the endpoint
289                         // to be backward compatable
290                         selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
291                     }
292                 }
293                 String tempDurable = (String) endpoint.getProperties().get(JmsConstants.DURABLE_PROPERTY);
294                 boolean durable = jmsConnector.isDurable();
295                 if (tempDurable != null)
296                 {
297                     durable = Boolean.valueOf(tempDurable);
298                 }
299 
300                 // Get the durable subscriber name if there is one
301                 String durableName = (String) endpoint.getProperties().get(JmsConstants.DURABLE_NAME_PROPERTY);
302                 if (durableName == null && durable && topic)
303                 {
304                     durableName = "mule." + jmsConnector.getName() + "." + endpoint.getEndpointURI().getAddress();
305                     logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
306                                  + durableName);
307                 }
308 
309                 // Create consumer
310                 consumer = jmsSupport.createConsumer(session, dest, selector, jmsConnector.isNoLocal(), durableName,
311                                                      topic, endpoint);
312             }
313             catch (JMSException e)
314             {
315                 throw new ConnectException(e, MultiConsumerJmsMessageReceiver.this);
316             }
317         }
318 
319         public void onMessage(final Message message)
320         {
321             try
322             {
323                 // Note: Despite the name "Worker", there is no new thread created here in order to maintain synchronicity for exception handling.  
324                 JmsWorker worker = new JmsWorker(message, MultiConsumerJmsMessageReceiver.this, this);
325                 worker.processMessages();
326             }
327             catch (Exception e)
328             {
329                 // Use this rollback method in case a transaction has not been configured on the endpoint.
330                 RollbackSourceCallback rollbackMethod = new RollbackSourceCallback()
331                 {                    
332                     public void rollback()
333                     {
334                         try
335                         {
336                             session.recover();
337                         }
338                         catch (JMSException jmsEx)
339                         {
340                             logger.error(jmsEx);
341                         }
342                     }
343                 };
344                 
345                 if (e instanceof MessagingException)
346                 {
347                     getFlowConstruct().getExceptionListener().handleException(e, ((MessagingException) e).getEvent(), rollbackMethod);
348                 }
349                 else
350                 {
351                     getConnector().getMuleContext().getExceptionListener().handleException(e, rollbackMethod);
352                 }
353             }
354         }
355     }
356 
357     protected class JmsWorker extends AbstractReceiverWorker
358     {
359         private final SubReceiver subReceiver;
360 
361         public JmsWorker(Message message, AbstractMessageReceiver receiver, SubReceiver subReceiver)
362         {
363             super(new ArrayList<Object>(1), receiver);
364             this.subReceiver = subReceiver;
365             messages.add(message);
366         }
367 
368         @Override
369         protected Object preProcessMessage(Object message) throws Exception
370         {
371             Message m = (Message) message;
372 
373             if (logger.isDebugEnabled())
374             {
375                 logger.debug("Message received it is of type: " +
376                              ClassUtils.getSimpleName(message.getClass()));
377                 if (m.getJMSDestination() != null)
378                 {
379                     logger.debug("Message received on " + m.getJMSDestination() + " ("
380                                  + m.getJMSDestination().getClass().getName() + ")");
381                 }
382                 else
383                 {
384                     logger.debug("Message received on unknown destination");
385                 }
386                 logger.debug("Message CorrelationId is: " + m.getJMSCorrelationID());
387                 logger.debug("Jms Message Id is: " + m.getJMSMessageID());
388             }
389 
390             if (m.getJMSRedelivered())
391             {
392                 // lazily create the redelivery handler
393                 RedeliveryHandler redeliveryHandler = jmsConnector.getRedeliveryHandlerFactory().create();
394                 redeliveryHandler.setConnector(jmsConnector);
395                 if (logger.isDebugEnabled())
396                 {
397                     logger.debug("Message with correlationId: " + m.getJMSCorrelationID()
398                                  + " has redelivered flag set, handing off to Redelivery Handler");
399                 }
400                 redeliveryHandler.handleRedelivery(m, receiver.getEndpoint(), receiver.getFlowConstruct());
401             }
402             return m;
403 
404         }
405 
406         @Override
407         protected void bindTransaction(Transaction tx) throws TransactionException
408         {
409             if (tx instanceof JmsTransaction || tx instanceof TransactionCollection)
410             {
411                 if (logger.isDebugEnabled())
412                 {
413                     logger.debug("Binding " + subReceiver.session + " to " + jmsConnector.getConnection());
414                 }
415                 tx.bindResource(jmsConnector.getConnection(), ReusableSessionWrapperFactory.createWrapper(subReceiver.session));
416             }
417             else
418             {
419                 if (tx instanceof JmsClientAcknowledgeTransaction)
420                 {
421                     //We should still bind the session to the transaction, but we also need the message itself
422                     //since that is the object that gets Acknowledged
423                     //tx.bindResource(jmsConnector.getConnection(), session);
424                     ((JmsClientAcknowledgeTransaction) tx).setMessage((Message) messages.get(0));
425                 }
426             }
427         }
428     }
429 
430 }