View Javadoc

1   /*
2    * $Id: SedaStageInterceptingMessageProcessor.java 20320 2010-11-24 15:03:31Z dfeist $
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.processor;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.api.MessagingException;
15  import org.mule.api.MuleContext;
16  import org.mule.api.MuleEvent;
17  import org.mule.api.MuleException;
18  import org.mule.api.NamedObject;
19  import org.mule.api.context.WorkManager;
20  import org.mule.api.context.WorkManagerSource;
21  import org.mule.api.exception.MessagingExceptionHandler;
22  import org.mule.api.exception.SystemExceptionHandler;
23  import org.mule.api.lifecycle.InitialisationException;
24  import org.mule.api.lifecycle.Lifecycle;
25  import org.mule.api.lifecycle.LifecycleException;
26  import org.mule.api.lifecycle.LifecycleState;
27  import org.mule.api.processor.MessageProcessor;
28  import org.mule.api.service.FailedToQueueEventException;
29  import org.mule.config.QueueProfile;
30  import org.mule.config.i18n.CoreMessages;
31  import org.mule.config.i18n.MessageFactory;
32  import org.mule.management.stats.QueueStatistics;
33  import org.mule.service.Pausable;
34  import org.mule.util.concurrent.WaitableBoolean;
35  import org.mule.util.queue.Queue;
36  import org.mule.util.queue.QueueSession;
37  import org.mule.work.AbstractMuleEventWork;
38  import org.mule.work.MuleWorkManager;
39  
40  import java.text.MessageFormat;
41  
42  import javax.resource.spi.work.Work;
43  import javax.resource.spi.work.WorkException;
44  import javax.resource.spi.work.WorkListener;
45  
46  /**
47   * Processes {@link MuleEvent}'s asynchronously using a {@link MuleWorkManager} to
48   * schedule asynchronous processing of the next {@link MessageProcessor}. 
49   */
50  public class SedaStageInterceptingMessageProcessor extends OptionalAsyncInterceptingMessageProcessor
51      implements WorkListener, Work, Lifecycle
52  {
53      protected static final String QUEUE_NAME_PREFIX = "seda.queue";
54  
55      protected QueueProfile queueProfile;
56      protected int queueTimeout;
57      protected LifecycleState lifecycleState;
58      protected QueueStatistics queueStatistics;
59      protected MuleContext muleContext;
60      protected String name;
61  
62      protected Queue queue;
63      private WaitableBoolean queueDraining = new WaitableBoolean(false);
64  
65      public SedaStageInterceptingMessageProcessor(String name,
66                                                   QueueProfile queueProfile,
67                                                   int queueTimeout,
68                                                   WorkManagerSource workManagerSource,
69                                                   LifecycleState lifecycleState,
70                                                   QueueStatistics queueStatistics,
71                                                   MuleContext muleContext)
72      {
73          super(workManagerSource);
74          this.name = name;
75          this.queueProfile = queueProfile;
76          this.queueTimeout = queueTimeout;
77          this.lifecycleState = lifecycleState;
78          this.queueStatistics = queueStatistics;
79          this.muleContext = muleContext;
80      }
81      
82      @Deprecated
83      public SedaStageInterceptingMessageProcessor(String name,
84                                                   QueueProfile queueProfile,
85                                                   int queueTimeout,
86                                                   WorkManagerSource workManagerSource,
87                                                   boolean doThreading,
88                                                   LifecycleState lifecycleState,
89                                                   QueueStatistics queueStatistics,
90                                                   MuleContext muleContext)
91      {
92          this(name, queueProfile, queueTimeout, workManagerSource, lifecycleState, queueStatistics,
93              muleContext);
94          this.doThreading = doThreading;
95      }
96  
97      @Override
98      protected void processNextAsync(MuleEvent event) throws MuleException
99      {
100         try
101         {
102             if (isStatsEnabled())
103             {
104                 queueStatistics.incQueuedEvent();
105             }
106             enqueue(event);
107         }
108         catch (Exception e)
109         {
110             throw new FailedToQueueEventException(
111                 CoreMessages.interruptedQueuingEventFor(getStageDescription()), event, e);
112         }
113 
114         if (logger.isTraceEnabled())
115         {
116             logger.trace("MuleEvent added to queue for: " + getStageDescription());
117         }
118     }
119 
120     protected boolean isStatsEnabled()
121     {
122         return queueStatistics != null && queueStatistics.isEnabled();
123     }
124 
125     protected void enqueue(MuleEvent event) throws Exception
126     {
127         if (logger.isDebugEnabled())
128         {
129             logger.debug(MessageFormat.format("{1}: Putting event on queue {2}", queue.getName(),
130                 getStageDescription(), event));
131         }
132         queue.put(event);
133     }
134 
135     protected MuleEvent dequeue() throws Exception
136     {
137         if (queue == null)
138         {
139             return null;
140         }
141         if (logger.isTraceEnabled())
142         {
143             logger.trace(MessageFormat.format("{0}: Polling queue {1}, timeout = {2}", getStageName(),
144                 getStageDescription(), queueTimeout));
145         }
146 
147         MuleEvent event = (MuleEvent)queue.poll(queueTimeout);
148         //If the service has been paused why the poll was waiting for an event to arrive on the queue,
149         //we put the object back on the queue
150         if(event!=null && lifecycleState.isPhaseComplete(Pausable.PHASE_NAME))
151         {
152             queue.untake(event);
153             return null;
154         }
155         return event;
156     }
157 
158     private class SedaStageWorker extends AbstractMuleEventWork
159     {
160         public SedaStageWorker(MuleEvent event)
161         {
162             super(event);
163         }
164 
165         @Override
166         protected void doRun()
167         {
168             try
169             {
170                 processNextTimed(event);
171             }
172             catch (Exception e)
173             {
174                 event.getSession().setValid(false);
175                 MessagingExceptionHandler exceptionListener = event.getFlowConstruct().getExceptionListener();
176                 if (e instanceof MessagingException)
177                 {
178                     exceptionListener.handleException(e, event);
179                 }
180                 else
181                 {
182                     exceptionListener.handleException(new MessagingException(
183                         CoreMessages.eventProcessingFailedFor(getStageDescription()), event, e), event);
184                 }
185             }
186         }
187     }
188 
189     /**
190      * While the service isn't stopped this runs a continuous loop checking for new
191      * events in the queue.
192      */
193     public void run()
194     {
195         DefaultMuleEvent event = null; 
196         QueueSession queueSession = muleContext.getQueueManager().getQueueSession();
197 
198         while (!lifecycleState.isStopped())
199         {
200             try
201             {
202                 // Wait if the service is paused
203                 if (lifecycleState.isPhaseComplete(Pausable.PHASE_NAME))
204                 {
205                     waitIfPaused();
206 
207                     // If service is resumed as part of stopping
208                     if (lifecycleState.isStopping())
209                     {
210                         queueDraining.set(true);
211                         if (!isQueuePersistent() && (queueSession != null && getQueueSize() > 0))
212                         {
213                             // Any messages in a non-persistent queue went paused
214                             // service is stopped are lost
215                             logger.warn(CoreMessages.stopPausedSedaStageNonPeristentQueueMessageLoss(
216                                 getQueueSize(), getQueueName()));
217                         }
218                         queueDraining.set(false);
219                         break;
220                     }
221                 }
222 
223                 // If we're doing a draining stop, read all events from the queue
224                 // before stopping
225                 if (lifecycleState.isStopping())
226                 {
227                     if (isQueuePersistent() || queueSession == null || getQueueSize() <= 0)
228                     {
229                         queueDraining.set(false);
230                         break;
231                     }
232                 }
233 
234                 event = (DefaultMuleEvent) dequeue();
235             }
236             catch (InterruptedException ie)
237             {
238                 queueDraining.set(false);
239                 break;
240             }
241             catch (Exception e)
242             {
243                 SystemExceptionHandler exceptionListener = muleContext.getExceptionListener();
244                 if (e instanceof MuleException)
245                 {
246                     exceptionListener.handleException(e);
247                 }
248                 else
249                 {
250                     exceptionListener.handleException(new MessagingException(
251                         CoreMessages.eventProcessingFailedFor(getStageDescription()),
252                         event, e));
253                 }
254             }
255 
256             if (event != null)
257             {
258                 try
259                 {
260                     if (isStatsEnabled())
261                     {
262                         queueStatistics.decQueuedEvent();
263                     }
264 
265                     if (logger.isDebugEnabled())
266                     {
267                         logger.debug(MessageFormat.format("{0}: Dequeued event from {1}",
268                             getStageDescription(), getQueueName()));
269                     }
270                     Work work = new SedaStageWorker(event);
271                     if (doThreading)
272                     {
273                         workManagerSource.getWorkManager().scheduleWork(work, WorkManager.INDEFINITE, null,
274                             this);
275                     }
276                     else
277                     {
278                         work.run();
279                     }
280                 }
281                 catch (Exception e)
282                 {
283                     event.getFlowConstruct().getExceptionListener().handleException(e, event);
284                 }
285             }
286         }
287     }
288 
289     /** Are the events in the SEDA queue persistent? */
290     protected boolean isQueuePersistent()
291     {
292         return queueProfile.isPersistent();
293     }
294 
295     public int getQueueSize()
296     {
297         return queue.size();
298     }
299 
300     protected String getQueueName()
301     {
302         return String.format("%s(%s)", QUEUE_NAME_PREFIX, getStageName());
303     }
304 
305     protected String getStageName()
306     {
307         if (name != null)
308         {
309             return name;
310         }
311         else if (next instanceof NamedObject)
312         {
313             return ((NamedObject) next).getName();
314         }
315         else
316         {
317             return String.format("%s.%s", next.getClass().getName(), next.hashCode());
318         }
319     }
320 
321     protected String getStageDescription()
322     {
323         return "SEDA Stage " + getStageName();
324     }
325 
326     protected void waitIfPaused() throws InterruptedException
327     {
328         if (logger.isDebugEnabled() && lifecycleState.isPhaseComplete(Pausable.PHASE_NAME))
329         {
330             logger.debug(getStageDescription() + " is paused. Polling halted until resumed is called");
331         }
332         while (lifecycleState.isPhaseComplete(Pausable.PHASE_NAME) && !lifecycleState.isStopping())
333         {
334             Thread.sleep(500);
335         }
336     }
337 
338     public void release()
339     {
340         queueDraining.set(false);
341     }
342 
343     public void initialise() throws InitialisationException
344     {
345         if (next == null)
346         {
347             throw new IllegalStateException(
348                 "Next message processor cannot be null with this InterceptingMessageProcessor");
349         }
350         // Setup event Queue
351         queueProfile.configureQueue(getQueueName(), muleContext.getQueueManager());
352         queue = muleContext.getQueueManager().getQueueSession().getQueue(getQueueName());
353         if (queue == null)
354         {
355             throw new InitialisationException(MessageFactory.createStaticMessage("Queue not created for "
356                                                                                  + getStageDescription()),
357                 this);
358         }
359     }
360 
361     public void start() throws MuleException
362     {
363         if (queue == null)
364         {
365             throw new IllegalStateException("Not initialised");
366         }
367         try
368         {
369             workManagerSource.getWorkManager().scheduleWork(this, WorkManager.INDEFINITE, null, this);
370         }
371         catch (WorkException e)
372         {
373             throw new LifecycleException(CoreMessages.failedToStart(getStageDescription()), e, this);
374 
375         }
376     }
377 
378     public void stop() throws MuleException
379     {
380         if (queue != null && queue.size() > 0)
381         {
382             try
383             {
384                 queueDraining.whenFalse(null);
385             }
386             catch (InterruptedException e)
387             {
388                 // we can ignore this
389             }
390         }
391     }
392 
393     public void dispose()
394     {
395         queue = null;
396     }
397 
398 }