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