View Javadoc

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