View Javadoc

1   /*
2    * $Id: MultiConsumerJmsMessageReceiver.java 20501 2010-12-08 00:52:19Z 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.MuleException;
14  import org.mule.api.MuleRuntimeException;
15  import org.mule.api.construct.FlowConstruct;
16  import org.mule.api.endpoint.InboundEndpoint;
17  import org.mule.api.lifecycle.CreateException;
18  import org.mule.api.lifecycle.LifecycleException;
19  import org.mule.api.transaction.Transaction;
20  import org.mule.api.transaction.TransactionException;
21  import org.mule.api.transport.Connector;
22  import org.mule.config.i18n.MessageFactory;
23  import org.mule.transaction.TransactionCollection;
24  import org.mule.transport.AbstractMessageReceiver;
25  import org.mule.transport.AbstractReceiverWorker;
26  import org.mule.transport.ConnectException;
27  import org.mule.transport.jms.filters.JmsSelectorFilter;
28  import org.mule.transport.jms.redelivery.RedeliveryHandler;
29  import org.mule.util.ClassUtils;
30  
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.util.List;
34  
35  import javax.jms.Destination;
36  import javax.jms.JMSException;
37  import javax.jms.Message;
38  import javax.jms.MessageConsumer;
39  import javax.jms.MessageListener;
40  import javax.jms.Session;
41  import javax.resource.spi.work.WorkException;
42  
43  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
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     @Override
164     protected boolean isDoStartMustFollowDoConnect()
165     {
166         return true;
167     }
168 
169     private class SubReceiver implements MessageListener
170     {
171         private final Log subLogger = LogFactory.getLog(getClass());
172 
173         private volatile Session session;
174         private volatile MessageConsumer consumer;
175 
176         protected volatile boolean connected;
177         protected volatile boolean started;
178         
179         protected void doConnect() throws MuleException
180         {
181             subLogger.debug("SUB doConnect()");
182             try
183             {
184                 createConsumer();
185             }
186             catch (Exception e)
187             {
188                 throw new LifecycleException(e, this);
189             }
190             connected = true;
191         }
192 
193         protected void doDisconnect() throws MuleException
194         {
195             subLogger.debug("SUB doDisconnect()");
196             if (started)
197             {
198                 doStop(true);
199             }
200             closeConsumer();
201             connected = false;
202         }
203 
204         protected void closeConsumer()
205         {
206             jmsConnector.closeQuietly(consumer);
207             consumer = null;
208             jmsConnector.closeQuietly(session);
209             session = null;
210         }
211 
212         protected void doStart() throws MuleException
213         {
214             subLogger.debug("SUB doStart()");
215             if (!connected)
216             {
217                 doConnect();
218             }
219             
220             try
221             { 
222                 consumer.setMessageListener(this);
223                 started = true;
224             }
225             catch (JMSException e)
226             {
227                 throw new LifecycleException(e, this);
228             }
229         }
230 
231         /**
232          * Stop the subreceiver.
233          * @param force - if true, any exceptions will be logged but the subreceiver will be considered stopped regardless
234          * @throws MuleException only if force = false
235          */
236         protected void doStop(boolean force) throws MuleException
237         {
238             subLogger.debug("SUB doStop()");
239 
240             if (consumer != null)
241             {
242                 try
243                 {
244                     consumer.setMessageListener(null);
245                     started = false;
246                 }
247                 catch (JMSException e)
248                 {
249                     if (force)
250                     {
251                         logger.warn("Unable to cleanly stop subreceiver: " + e.getMessage());
252                         started = false;
253                     }
254                     else
255                     {
256                         throw new LifecycleException(e, this);
257                     }
258                 }
259             }
260         }
261 
262         /**
263          * Create a consumer for the jms destination.
264          */
265         protected void createConsumer() throws Exception
266         {
267             subLogger.debug("SUB createConsumer()");
268             
269             try
270             {
271                 JmsSupport jmsSupport = jmsConnector.getJmsSupport();
272                 boolean topic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
273 
274                 // Create session if none exists
275                 if (session == null)
276                 {
277                     session = jmsConnector.getSession(endpoint);
278                 }
279 
280                 // Create destination
281                 Destination dest = jmsSupport.createDestination(session, endpoint);
282 
283                 // Extract jms selector
284                 String selector = null;
285                 if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
286                 {
287                     selector = ((JmsSelectorFilter) endpoint.getFilter()).getExpression();
288                 }
289                 else
290                 {
291                     if (endpoint.getProperties() != null)
292                     {
293                         // still allow the selector to be set as a property on the endpoint
294                         // to be backward compatable
295                         selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
296                     }
297                 }
298                 String tempDurable = (String) endpoint.getProperties().get(JmsConstants.DURABLE_PROPERTY);
299                 boolean durable = jmsConnector.isDurable();
300                 if (tempDurable != null)
301                 {
302                     durable = Boolean.valueOf(tempDurable);
303                 }
304 
305                 // Get the durable subscriber name if there is one
306                 String durableName = (String) endpoint.getProperties().get(JmsConstants.DURABLE_NAME_PROPERTY);
307                 if (durableName == null && durable && topic)
308                 {
309                     durableName = "mule." + jmsConnector.getName() + "." + endpoint.getEndpointURI().getAddress();
310                     logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
311                                  + durableName);
312                 }
313 
314                 // Create consumer
315                 consumer = jmsSupport.createConsumer(session, dest, selector, jmsConnector.isNoLocal(), durableName,
316                                                      topic, endpoint);
317             }
318             catch (JMSException e)
319             {
320                 throw new ConnectException(e, MultiConsumerJmsMessageReceiver.this);
321             }
322         }
323 
324         public void onMessage(final Message message)
325         {
326             try
327             {
328                 // This must be the doWork() to preserve the transactional context.
329                 // We are already running in the consumer thread by this time.
330                 // The JmsWorker class is a one-off executor which is abandoned after it's done 
331                 // and is easily garbage-collected (confirmed with a profiler)
332                 getWorkManager().doWork(new JmsWorker(message, MultiConsumerJmsMessageReceiver.this, this));
333             }
334             catch (WorkException e)
335             {
336                 throw new MuleRuntimeException(MessageFactory.createStaticMessage(
337                         "Couldn't submit a work item to the WorkManager"), e);
338             }
339         }
340     }
341 
342     protected class JmsWorker extends AbstractReceiverWorker
343     {
344         private final SubReceiver subReceiver;
345 
346         public JmsWorker(Message message, AbstractMessageReceiver receiver, SubReceiver subReceiver)
347         {
348             super(new ArrayList<Object>(1), receiver);
349             this.subReceiver = subReceiver;
350             messages.add(message);
351         }
352 
353         @Override
354         protected Object preProcessMessage(Object message) throws Exception
355         {
356             Message m = (Message) message;
357 
358             if (logger.isDebugEnabled())
359             {
360                 logger.debug("Message received it is of type: " +
361                              ClassUtils.getSimpleName(message.getClass()));
362                 if (m.getJMSDestination() != null)
363                 {
364                     logger.debug("Message received on " + m.getJMSDestination() + " ("
365                                  + m.getJMSDestination().getClass().getName() + ")");
366                 }
367                 else
368                 {
369                     logger.debug("Message received on unknown destination");
370                 }
371                 logger.debug("Message CorrelationId is: " + m.getJMSCorrelationID());
372                 logger.debug("Jms Message Id is: " + m.getJMSMessageID());
373             }
374 
375             if (m.getJMSRedelivered())
376             {
377                 // lazily create the redelivery handler
378                 RedeliveryHandler redeliveryHandler = jmsConnector.getRedeliveryHandlerFactory().create();
379                 redeliveryHandler.setConnector(jmsConnector);
380                 if (logger.isDebugEnabled())
381                 {
382                     logger.debug("Message with correlationId: " + m.getJMSCorrelationID()
383                                  + " has redelivered flag set, handing off to Redelivery Handler");
384                 }
385                 redeliveryHandler.handleRedelivery(m, receiver.getEndpoint(), receiver.getFlowConstruct());
386             }
387             return m;
388 
389         }
390 
391         @Override
392         protected void bindTransaction(Transaction tx) throws TransactionException
393         {
394             if (tx instanceof JmsTransaction || tx instanceof TransactionCollection)
395             {
396                 if (logger.isDebugEnabled())
397                 {
398                     logger.debug("Binding " + subReceiver.session + " to " + jmsConnector.getConnection());
399                 }
400                 tx.bindResource(jmsConnector.getConnection(), subReceiver.session);
401             }
402             else
403             {
404                 if (tx instanceof JmsClientAcknowledgeTransaction)
405                 {
406                     //We should still bind the session to the transaction, but we also need the message itself
407                     //since that is the object that gets Acknowledged
408                     //tx.bindResource(jmsConnector.getConnection(), session);
409                     ((JmsClientAcknowledgeTransaction) tx).setMessage((Message) messages.get(0));
410                 }
411             }
412         }
413     }
414 
415 }