View Javadoc

1   /*
2    * $Id: MultiConsumerJmsMessageReceiver.java 11597 2008-04-18 17:40:33Z aperepel $
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.api.MuleException;
14  import org.mule.api.MuleRuntimeException;
15  import org.mule.api.endpoint.InboundEndpoint;
16  import org.mule.api.lifecycle.CreateException;
17  import org.mule.api.lifecycle.LifecycleException;
18  import org.mule.api.service.Service;
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.transport.AbstractMessageReceiver;
24  import org.mule.transport.AbstractReceiverWorker;
25  import org.mule.transport.ConnectException;
26  import org.mule.transport.jms.filters.JmsSelectorFilter;
27  import org.mule.util.ClassUtils;
28  
29  import java.util.ArrayList;
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.jms.Topic;
38  import javax.resource.spi.work.WorkException;
39  
40  import edu.emory.mathcs.backport.java.util.concurrent.BlockingDeque;
41  import edu.emory.mathcs.backport.java.util.concurrent.LinkedBlockingDeque;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  
45  /**
46   * This is an experimental rework of the <code>transacted.message.receiver</code>
47   * (it may work with non-transacted sessions as well,
48   * just hasn't been tested and load-tested yet.
49   * <p/>
50   * In Mule an endpoint corresponds to a single receiver. It's up to the receiver to do multithreaded consumption and
51   * resource allocation, if needed. This class honors the <code>numberOfConcurrentTransactedReceivers</code> strictly
52   * and will create exactly this number of consumers.
53   */
54  public class MultiConsumerJmsMessageReceiver extends AbstractMessageReceiver
55  {
56      protected final BlockingDeque consumers;
57  
58      protected volatile int receiversCount;
59  
60      private final JmsConnector jmsConnector;
61  
62      public MultiConsumerJmsMessageReceiver(Connector connector, Service service, InboundEndpoint endpoint)
63              throws CreateException
64      {
65          super(connector, service, endpoint);
66  
67          jmsConnector = (JmsConnector) connector;
68  
69          final boolean isTopic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
70          receiversCount = jmsConnector.getNumberOfConcurrentTransactedReceivers();
71          if (isTopic && receiversCount != 1)
72          {
73              if (logger.isInfoEnabled())
74              {
75                  logger.info("Destination " + getEndpoint().getEndpointURI() + " is a topic, but " + receiversCount +
76                                  " receivers have been configured. Will configure only 1.");
77              }
78              receiversCount = 1;
79          }
80          if (logger.isDebugEnabled())
81          {
82              logger.debug("Creating " + receiversCount + " sub-receivers for " + endpoint.getEndpointURI());
83          }
84          consumers = new LinkedBlockingDeque(receiversCount);
85          for (int i = 0; i < receiversCount; i++)
86          {
87              consumers.addLast(new SubReceiver());
88          }
89      }
90  
91      protected void doStart() throws MuleException
92      {
93          logger.debug("doStart()");
94          for (int i = 0; i < receiversCount; i++)
95          {
96              SubReceiver sub = (SubReceiver) consumers.removeFirst();
97              sub.doStart();
98              consumers.addLast(sub);
99          }
100     }
101 
102     protected void doStop() throws MuleException
103     {
104         logger.debug("doStop()");
105         for (int i = 0; i < receiversCount; i++)
106         {
107             SubReceiver sub = (SubReceiver) consumers.removeFirst();
108             sub.doStop();
109             consumers.addLast(sub);
110         }
111     }
112 
113     protected void doConnect() throws Exception
114     {
115         logger.debug("doConnect()");
116         for (int i = 0; i < receiversCount; i++)
117         {
118             SubReceiver sub = (SubReceiver) consumers.removeFirst();
119             sub.doConnect();
120             consumers.addLast(sub);
121         }
122     }
123 
124     protected void doDisconnect() throws Exception
125     {
126         logger.debug("doDisconnect()");
127         for (int i = 0; i < receiversCount; i++)
128         {
129             SubReceiver sub = (SubReceiver) consumers.removeFirst();
130             sub.doDisconnect();
131             consumers.addLast(sub);
132         }
133 
134     }
135 
136     protected void doDispose()
137     {
138         logger.debug("doDispose()");
139     }
140 
141     private class SubReceiver implements MessageListener
142     {
143         private final Log subLogger = LogFactory.getLog(getClass());
144 
145         private volatile Session session;
146         private volatile MessageConsumer consumer;
147 
148         protected void doConnect() throws Exception
149         {
150             subLogger.debug("SUB doConnect()");
151             createConsumer();
152         }
153 
154         protected void doDisconnect() throws Exception
155         {
156             closeConsumer();
157         }
158 
159         protected void closeConsumer()
160         {
161             jmsConnector.closeQuietly(consumer);
162             consumer = null;
163             jmsConnector.closeQuietly(session);
164             session = null;
165         }
166 
167         protected void doStart() throws MuleException
168         {
169             try
170             {
171                 consumer.setMessageListener(this);
172             }
173             catch (JMSException e)
174             {
175                 throw new LifecycleException(e, this);
176             }
177         }
178 
179         protected void doStop() throws MuleException
180         {
181             try
182             {
183                 if (consumer != null)
184                 {
185                     consumer.setMessageListener(null);
186                 }
187             }
188             catch (JMSException e)
189             {
190                 throw new LifecycleException(e, this);
191             }
192         }
193 
194 
195         /**
196          * Create a consumer for the jms destination.
197          */
198         protected void createConsumer() throws Exception
199         {
200             try
201             {
202                 JmsSupport jmsSupport = jmsConnector.getJmsSupport();
203                 // Create session if none exists
204                 if (session == null)
205                 {
206                     session = jmsConnector.getSession(endpoint);
207                 }
208 
209                 boolean topic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
210 
211                 // Create destination
212                 Destination dest = jmsSupport.createDestination(session, endpoint.getEndpointURI().getAddress(),
213                                                                 topic);
214 
215                 // Extract jms selector
216                 String selector = null;
217                 if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
218                 {
219                     selector = ((JmsSelectorFilter) endpoint.getFilter()).getExpression();
220                 }
221                 else
222                 {
223                     if (endpoint.getProperties() != null)
224                     {
225                         // still allow the selector to be set as a property on the endpoint
226                         // to be backward compatable
227                         selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
228                     }
229                 }
230                 String tempDurable = (String) endpoint.getProperties().get(JmsConstants.DURABLE_PROPERTY);
231                 boolean durable = jmsConnector.isDurable();
232                 if (tempDurable != null)
233                 {
234                     durable = Boolean.valueOf(tempDurable).booleanValue();
235                 }
236 
237                 // Get the durable subscriber name if there is one
238                 String durableName = (String) endpoint.getProperties().get(JmsConstants.DURABLE_NAME_PROPERTY);
239                 if (durableName == null && durable && dest instanceof Topic)
240                 {
241                     durableName = "mule." + jmsConnector.getName() + "." + endpoint.getEndpointURI().getAddress();
242                     logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
243                                  + durableName);
244                 }
245 
246                 // Create consumer
247                 consumer = jmsSupport.createConsumer(session, dest, selector, jmsConnector.isNoLocal(), durableName,
248                                                      topic);
249             }
250             catch (JMSException e)
251             {
252                 throw new ConnectException(e, MultiConsumerJmsMessageReceiver.this);
253             }
254         }
255 
256         public void onMessage(final Message message)
257         {
258             try
259             {
260                 // This must be the doWork() to preserve the transactional context.
261                 // We are already running in the consumer thread by this time.
262                 // The JmsWorker classe is a one-off executor which is abandoned after it's done and is
263                 // easily garbage-collected (confirmed with a profiler)
264                 getWorkManager().doWork(new JmsWorker(message, MultiConsumerJmsMessageReceiver.this, this));
265             }
266             catch (WorkException e)
267             {
268                 throw new MuleRuntimeException(MessageFactory.createStaticMessage(
269                         "Couldn't submit a work item to the WorkManager"), e);
270             }
271         }
272     }
273 
274     protected class JmsWorker extends AbstractReceiverWorker
275     {
276         private final SubReceiver subReceiver;
277 
278         public JmsWorker(Message message, AbstractMessageReceiver receiver, SubReceiver subReceiver)
279         {
280             super(new ArrayList(1), receiver);
281             this.subReceiver = subReceiver;
282             messages.add(message);
283         }
284 
285         //@Override
286         protected Object preProcessMessage(Object message) throws Exception
287         {
288             Message m = (Message) message;
289 
290             if (logger.isDebugEnabled())
291             {
292                 logger.debug("Message received it is of type: " +
293                              ClassUtils.getSimpleName(message.getClass()));
294                 if (m.getJMSDestination() != null)
295                 {
296                     logger.debug("Message received on " + m.getJMSDestination() + " ("
297                                  + m.getJMSDestination().getClass().getName() + ")");
298                 }
299                 else
300                 {
301                     logger.debug("Message received on unknown destination");
302                 }
303                 logger.debug("Message CorrelationId is: " + m.getJMSCorrelationID());
304                 logger.debug("Jms Message Id is: " + m.getJMSMessageID());
305             }
306 
307             if (m.getJMSRedelivered())
308             {
309                 // lazily create the redelivery handler
310                 RedeliveryHandler redeliveryHandler = jmsConnector.getRedeliveryHandlerFactory().create();
311                 redeliveryHandler.setConnector(jmsConnector);
312                 if (logger.isDebugEnabled())
313                 {
314                     logger.debug("Message with correlationId: " + m.getJMSCorrelationID()
315                                  + " has redelivered flag set, handing off to Exception Handler");
316                 }
317                 redeliveryHandler.handleRedelivery(m);
318             }
319             return m;
320 
321         }
322 
323         protected void bindTransaction(Transaction tx) throws TransactionException
324         {
325             if (tx instanceof JmsTransaction)
326             {
327                 if (logger.isDebugEnabled())
328                 {
329                     logger.debug(">>> !!  Binding " + subReceiver.session + " to " + jmsConnector.getConnection());
330                 }
331                 tx.bindResource(jmsConnector.getConnection(), subReceiver.session);
332             }
333             else
334             {
335                 if (tx instanceof JmsClientAcknowledgeTransaction)
336                 {
337                     //We should still bind the session to the transaction, but we also need the message itself
338                     //since that is the object that gets Acknowledged
339                     //tx.bindResource(jmsConnector.getConnection(), session);
340                     ((JmsClientAcknowledgeTransaction) tx).setMessage((Message) messages.get(0));
341                 }
342             }
343         }
344     }
345 
346 }