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