View Javadoc

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