View Javadoc

1   /*
2    * $Id: SedaService.java 12269 2008-07-10 04:19:03Z dfeist $
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.model.seda;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.DefaultMuleMessage;
15  import org.mule.FailedToQueueEventException;
16  import org.mule.OptimizedRequestContext;
17  import org.mule.RequestContext;
18  import org.mule.api.ExceptionPayload;
19  import org.mule.api.MessagingException;
20  import org.mule.api.MuleEvent;
21  import org.mule.api.MuleException;
22  import org.mule.api.MuleMessage;
23  import org.mule.api.MuleRuntimeException;
24  import org.mule.api.config.ThreadingProfile;
25  import org.mule.api.context.WorkManager;
26  import org.mule.api.endpoint.InboundEndpoint;
27  import org.mule.api.lifecycle.InitialisationException;
28  import org.mule.api.lifecycle.LifecycleException;
29  import org.mule.api.service.ServiceException;
30  import org.mule.api.transport.ReplyToHandler;
31  import org.mule.config.QueueProfile;
32  import org.mule.config.i18n.CoreMessages;
33  import org.mule.config.i18n.MessageFactory;
34  import org.mule.management.stats.ServiceStatistics;
35  import org.mule.message.DefaultExceptionPayload;
36  import org.mule.service.AbstractService;
37  import org.mule.transport.NullPayload;
38  import org.mule.util.queue.Queue;
39  import org.mule.util.queue.QueueSession;
40  
41  import java.util.NoSuchElementException;
42  
43  import javax.resource.spi.work.Work;
44  import javax.resource.spi.work.WorkEvent;
45  import javax.resource.spi.work.WorkException;
46  import javax.resource.spi.work.WorkListener;
47  
48  /**
49   * A Seda service runs inside a Seda Model and is responsible for managing a Seda
50   * Queue and thread pool for a Mule sevice service. In Seda terms this is
51   * equivilent to a stage.
52   */
53  public class SedaService extends AbstractService implements Work, WorkListener
54  {
55      /**
56       * Serial version/
57       */
58      private static final long serialVersionUID = 7711976708670893015L;
59      
60      private static final String QUEUE_NAME_SUFFIX = ".component";
61  
62      protected WorkManager workManager;
63  
64      /**
65       * The time out used for taking from the Seda Queue.
66       */
67      protected Integer queueTimeout;
68  
69      /**
70       * The threading profile to use for this service. If this is not set a default
71       * will be provided by the server
72       */
73      protected ThreadingProfile threadingProfile;
74  
75      /**
76       * The queue profile to use for this service. If this is not set a default
77       * will be provided by the server
78       */
79      protected QueueProfile queueProfile;
80      
81      protected Queue queue;
82  
83      /** For Spring only */
84      public SedaService()
85      {
86          super();
87      }
88      
89      /**
90       * Initialise the service. The service will first create a Mule UMO from the
91       * UMODescriptor and then initialise a pool based on the attributes in the
92       * UMODescriptor.
93       * 
94       * @throws org.mule.api.lifecycle.InitialisationException if the service fails
95       *             to initialise
96       * @see org.mule.api.UMODescriptor
97       */
98      protected synchronized void doInitialise() throws InitialisationException
99      {
100         if (threadingProfile == null)
101         {
102             // TODO MULE-2102 This should be configured in the default template.
103             threadingProfile = muleContext.getDefaultComponentThreadingProfile();
104         }
105         // Create thread pool
106         workManager = threadingProfile.createWorkManager(getName());
107 
108         if (queueProfile == null)
109         {
110             // TODO MULE-2102 This should be configured in the default template.
111             queueProfile = ((SedaModel) model).getQueueProfile();
112         }
113         
114         if (queueTimeout == null)
115         {
116             // TODO MULE-2102 This should be configured in the default template.
117             setQueueTimeout(new Integer(((SedaModel) model).getQueueTimeout()));
118         }
119         
120         try
121         {
122             if (name == null)
123             {
124                 throw new InitialisationException(MessageFactory.createStaticMessage("Service has no name to identify it"), this);
125             }
126             // Setup event Queue (used for VM execution).  The queue has the same name as the service.
127             queueProfile.configureQueue(name, muleContext.getQueueManager());
128             queue = muleContext.getQueueManager().getQueueSession().getQueue(name + QUEUE_NAME_SUFFIX);
129             if (queue == null)
130             {
131                 throw new InitialisationException(MessageFactory.createStaticMessage("Queue not created for service " + name), this);
132             }
133         }
134         catch (InitialisationException e)
135         {
136             throw e;
137         }
138         catch (Throwable e)
139         {
140             throw new InitialisationException(
141                 CoreMessages.objectFailedToInitialise("Service Queue"), e, this);
142         }
143     }
144 
145     protected void doForceStop() throws MuleException
146     {
147         doStop();
148     }
149 
150     protected void doStop() throws MuleException
151     {
152         if (queue != null && queue.size() > 0)
153         {
154             try
155             {
156                 stopping.whenFalse(null);
157             }
158             catch (InterruptedException e)
159             {
160                 // we can ignore this
161                 // TODO MULE-863: Why?
162             }
163         }
164         workManager.dispose();
165     }
166 
167     protected void doStart() throws MuleException
168     {
169         try
170         {
171             workManager.start();
172             workManager.scheduleWork(this, WorkManager.INDEFINITE, null, this);
173         }
174         catch (Exception e)
175         {
176             throw new LifecycleException(
177                 CoreMessages.failedToStart("Service: " + name), e, this);
178         }
179     }
180 
181     protected void doDispose()
182     {
183         queue = null;
184         // threadPool.awaitTerminationAfterShutdown();
185         if (workManager != null)
186         {
187             workManager.dispose();
188         }
189     }
190 
191     protected void doDispatch(MuleEvent event) throws MuleException
192     {
193         // Dispatching event to the service
194         if (stats.isEnabled())
195         {
196             stats.incReceivedEventASync();
197         }
198         if (logger.isDebugEnabled())
199         {
200             logger.debug("Service: " + name + " has received asynchronous event on: "
201                          + event.getEndpoint().getEndpointURI());
202         }
203 
204         // Block until we can queue the next event
205         try
206         {
207             enqueue(event);
208             if (stats.isEnabled())
209             {
210                 stats.incQueuedEvent();
211             }
212         }
213         catch (Exception e)
214         {
215             FailedToQueueEventException e1 = new FailedToQueueEventException(
216                 CoreMessages.interruptedQueuingEventFor(this.getName()), event.getMessage(), this, e);
217             handleException(e1);
218         }
219 
220         if (logger.isTraceEnabled())
221         {
222             logger.trace("MuleEvent added to queue for: " + name);
223         }
224     }
225 
226     protected MuleMessage doSend(MuleEvent event) throws MuleException
227     {
228         MuleMessage result = null;
229         try
230         {
231             if (logger.isDebugEnabled())
232             {
233                 logger.debug(this + " : got proxy for " + event.getId() + " = " + component);
234             }
235             Object replyTo = event.getMessage().getReplyTo();
236             ReplyToHandler replyToHandler = getReplyToHandler(event.getMessage(), (InboundEndpoint) event.getEndpoint());
237             result = component.onCall(event);
238             result = sendToOutboundRouter(event, result);
239             result = processAsyncReplyRouter(result);
240             processReplyTo(event, result, replyToHandler, replyTo);
241             // stats
242             if (stats.isEnabled())
243             {
244                 stats.incSentEventSync();
245             }
246         }
247         catch (Exception e)
248         {
249             event.getSession().setValid(false);
250             if (e instanceof MessagingException)
251             {
252                 handleException(e);
253             }
254             else
255             {
256                 handleException(new MessagingException(CoreMessages.eventProcessingFailedFor(getName()),
257                     event.getMessage(), e));
258             }
259             if (result == null)
260             {
261                 // important that we pull event from request context here as it may
262                 // have been modified
263                 // (necessary to avoid scribbling between threads)
264                 result = new DefaultMuleMessage(NullPayload.getInstance(), RequestContext.getEvent().getMessage());
265             }
266             ExceptionPayload exceptionPayload = result.getExceptionPayload();
267             if (exceptionPayload == null)
268             {
269                 exceptionPayload = new DefaultExceptionPayload(e);
270             }
271             result.setExceptionPayload(exceptionPayload);
272         }
273         return result;
274     }
275 
276     public int getQueueSize()
277     {
278         if (queue == null)
279         {
280             logger.warn(new InitialisationException(MessageFactory.createStaticMessage("Queue not created for service " + name), this));
281             return -1;
282         }
283         return queue.size();
284     }
285 
286     /**
287      * While the service isn't stopped this runs a continuous loop checking for new
288      * events in the queue.
289      */
290     public void run()
291     {
292         DefaultMuleEvent event = null;
293         QueueSession queueSession = muleContext.getQueueManager().getQueueSession();
294 
295         while (!stopped.get())
296         {
297             try
298             {
299                 // Wait if the service is paused
300                 paused.whenFalse(null);
301 
302                 // If we're doing a draining stop, read all events from the queue
303                 // before stopping
304                 if (stopping.get())
305                 {
306                     if (queueSession == null || getQueueSize() <= 0)
307                     {
308                         stopping.set(false);
309                         break;
310                     }
311                 }
312 
313                 event = (DefaultMuleEvent) dequeue();
314                 if (event != null)
315                 {
316                     if (stats.isEnabled())
317                     {
318                         stats.decQueuedEvent();
319                     }
320 
321                     if (logger.isDebugEnabled())
322                     {
323                         logger.debug("Service: " + name + " dequeued event on: "
324                                         + event.getEndpoint().getEndpointURI());
325                     }
326                     workManager.scheduleWork(new ComponentStageWorker(event), WorkManager.INDEFINITE, null, this);
327                 }
328             }
329             catch (Exception e)
330             {
331                 if (isStopped() || isStopping())
332                 {
333                     break;
334                 }
335 
336                 if (e instanceof InterruptedException)
337                 {
338                     stopping.set(false);
339                     break;
340                 }
341                 else if (e instanceof NoSuchElementException)
342                 {
343                     handleException(new ServiceException(CoreMessages.proxyPoolTimedOut(),
344                         (event == null ? null : event.getMessage()), this, e));
345                 }
346                 else if (e instanceof MuleException)
347                 {
348                     handleException(e);
349                 }
350                 else if (e instanceof WorkException)
351                 {
352                     handleException(
353                         new ServiceException(
354                             CoreMessages.eventProcessingFailedFor(name),
355                             (event == null ? null : event.getMessage()), this, e));
356                 }
357                 else
358                 {
359                     handleException(
360                         new ServiceException(
361                             CoreMessages.failedToGetPooledObject(),
362                             (event == null ? null : event.getMessage()), this, e));
363                 }
364             }
365             finally
366             {
367                 stopping.set(false);
368             }
369         }
370     }
371 
372     public void release()
373     {
374         stopping.set(false);
375     }
376 
377     protected void enqueue(MuleEvent event) throws Exception
378     {
379         if (queue == null)
380         {
381             throw new InitialisationException(MessageFactory.createStaticMessage("Queue not created for service " + name), this);
382         }
383         if (logger.isDebugEnabled())
384         {
385             logger.debug("Service " + name + " putting event on queue " + queue.getName() + ": " + event);
386         }
387         queue.put(event);
388     }
389 
390     protected MuleEvent dequeue() throws Exception
391     {
392         if (queue == null)
393         {
394             throw new InitialisationException(MessageFactory.createStaticMessage("Queue not created for service " + name), this);
395         }
396         if (logger.isDebugEnabled())
397         {
398             logger.debug("Service " + name + " polling queue " + queue.getName() + ", timeout = " + queueTimeout);
399         }
400         if (getQueueTimeout() == null)
401         {
402             throw new InitialisationException(CoreMessages.noServiceQueueTimeoutSet(this), this);
403         }
404         else
405         {
406             return (MuleEvent) queue.poll(getQueueTimeout().intValue());
407         }
408     }
409 
410     public void workAccepted(WorkEvent event)
411     {
412         handleWorkException(event, "workAccepted");
413     }
414 
415     public void workRejected(WorkEvent event)
416     {
417         handleWorkException(event, "workRejected");
418     }
419 
420     public void workStarted(WorkEvent event)
421     {
422         handleWorkException(event, "workStarted");
423     }
424 
425     public void workCompleted(WorkEvent event)
426     {
427         handleWorkException(event, "workCompleted");
428     }
429 
430     protected void handleWorkException(WorkEvent event, String type)
431     {
432         Throwable e;
433 
434         if (event != null && event.getException() != null)
435         {
436             e = event.getException();
437         }
438         else
439         {
440             return;
441         }
442 
443         if (event.getException().getCause() != null)
444         {
445             e = event.getException().getCause();
446         }
447 
448         logger.error("Work caused exception on '" + type + "'. Work being executed was: "
449                         + event.getWork().toString());
450 
451         if (e instanceof Exception)
452         {
453             handleException((Exception) e);
454         }
455         else
456         {
457             throw new MuleRuntimeException(
458                 CoreMessages.componentCausedErrorIs(this.getName()), e);
459         }
460     }
461 
462     protected ServiceStatistics createStatistics()
463     {
464         return new ServiceStatistics(getName(), threadingProfile.getMaxThreadsActive());
465     }
466 
467     public Object getInstance() throws MuleException
468     {
469         throw new UnsupportedOperationException("Direct access to underlying service object is not allowed in the SedaModel.  If this is for a unit test, make sure you are using the TestSedaModel ('seda-test')");
470     }
471 
472     public QueueProfile getQueueProfile()
473     {
474         return queueProfile;
475     }
476 
477     public void setQueueProfile(QueueProfile queueProfile)
478     {
479         this.queueProfile = queueProfile;
480     }
481 
482     public Integer getQueueTimeout()
483     {
484         return queueTimeout;
485     }
486 
487     public void setQueueTimeout(Integer queueTimeout)
488     {
489         this.queueTimeout = queueTimeout;
490     }
491 
492     public ThreadingProfile getThreadingProfile()
493     {
494         return threadingProfile;
495     }
496 
497     public void setThreadingProfile(ThreadingProfile threadingProfile)
498     {
499         this.threadingProfile = threadingProfile;
500     }
501 
502     public WorkManager getWorkManager()
503     {
504         return workManager;
505     }
506 
507     public void setWorkManager(WorkManager workManager)
508     {
509         this.workManager = workManager;
510     }
511 
512     protected void dispatchToOutboundRouter(MuleEvent event, MuleMessage result) throws MessagingException
513     {
514         super.dispatchToOutboundRouter(event, result);
515         // TODO MULE-3077 SedaService should use a SEDA queue to dispatch to outbound
516         // routers
517     }
518 
519     private class ComponentStageWorker implements Work
520     {
521         private MuleEvent event;
522 
523         public ComponentStageWorker(MuleEvent event)
524         {
525             this.event = event;
526         }
527 
528         public void run()
529         {
530             try
531             {
532                 event = OptimizedRequestContext.criticalSetEvent(event);
533                 Object replyTo = event.getMessage().getReplyTo();
534                 ReplyToHandler replyToHandler = getReplyToHandler(event.getMessage(),
535                     (InboundEndpoint) event.getEndpoint());
536                 MuleMessage result = component.onCall(event);
537                 dispatchToOutboundRouter(event, result);
538                 processReplyTo(event, result, replyToHandler, replyTo);
539             }
540             catch (Exception e)
541             {
542                 event.getSession().setValid(false);
543                 if (e instanceof MessagingException)
544                 {
545                     handleException(e);
546                 }
547                 else
548                 {
549                     handleException(new MessagingException(CoreMessages.eventProcessingFailedFor(getName()),
550                         event.getMessage(), e));
551                 }
552             }
553         }
554 
555         public void release()
556         {
557             // no-op
558         }
559     }
560 }