View Javadoc

1   /*
2    * $Id: SedaComponent.java 11728 2008-05-13 07:31:11Z dirk.olmes $
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.impl.model.seda;
12  
13  import org.mule.MuleManager;
14  import org.mule.MuleRuntimeException;
15  import org.mule.config.PoolingProfile;
16  import org.mule.config.QueueProfile;
17  import org.mule.config.ThreadingProfile;
18  import org.mule.config.i18n.CoreMessages;
19  import org.mule.impl.FailedToQueueEventException;
20  import org.mule.impl.MuleDescriptor;
21  import org.mule.impl.MuleEvent;
22  import org.mule.impl.model.AbstractComponent;
23  import org.mule.impl.model.DefaultMuleProxy;
24  import org.mule.impl.model.MuleProxy;
25  import org.mule.management.stats.ComponentStatistics;
26  import org.mule.management.stats.SedaComponentStatistics;
27  import org.mule.umo.ComponentException;
28  import org.mule.umo.UMOEvent;
29  import org.mule.umo.UMOException;
30  import org.mule.umo.UMOMessage;
31  import org.mule.umo.lifecycle.InitialisationException;
32  import org.mule.umo.lifecycle.LifecycleException;
33  import org.mule.umo.manager.UMOWorkManager;
34  import org.mule.util.ObjectPool;
35  import org.mule.util.queue.QueueSession;
36  
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.NoSuchElementException;
40  
41  import javax.resource.spi.work.Work;
42  import javax.resource.spi.work.WorkEvent;
43  import javax.resource.spi.work.WorkException;
44  import javax.resource.spi.work.WorkListener;
45  import javax.resource.spi.work.WorkManager;
46  
47  /**
48   * A Seda component runs inside a Seda Model and is responsible for managing a Seda
49   * Queue and thread pool for a Mule sevice component. In Seda terms this is
50   * equivilent to a stage.
51   */
52  public class SedaComponent extends AbstractComponent implements Work, WorkListener
53  {
54      public static final String QUEUE_PROFILE_PROPERTY = "queueProfile";
55      public static final String POOLING_PROFILE_PROPERTY = "poolingProfile";
56      /**
57       * Serial version/
58       */
59      private static final long serialVersionUID = 7711976708670893015L;
60  
61      /**
62       * A pool of available Mule proxies. If component pooling has been disabled on the
63       * SEDAModel, this pool will be null and the 'componentProxy' will be used.
64       */
65      protected ObjectPool proxyPool;
66  
67      /**
68       * Is created only if component pooling is turned off on the SEDAModel. In this
69       * scenario all requests are serviced by this component, unless
70       * {@link #componentPerRequest} flag is set on the model.
71       */
72      protected MuleProxy componentProxy;
73  
74      protected UMOWorkManager workManager;
75  
76      protected String descriptorQueueName;
77  
78      /**
79       * The time out used for taking from the Seda Queue.
80       */
81      protected int queueTimeout = 0;
82  
83      /**
84       * Whether component objects should be pooled or a single instance should be
85       * used.
86       */
87      protected boolean enablePooling = true;
88  
89      /**
90       * If this is set to true a new component will be created for every request.
91       */
92      protected boolean componentPerRequest = false;
93  
94      /**
95       * The pooling configuration used when initialising the component described by
96       * this descriptor.
97       */
98      protected PoolingProfile poolingProfile;
99  
100     /**
101      * The queuing profile for events received for this component.
102      */
103     protected QueueProfile queueProfile;
104 
105     /**
106      * Creates a new SEDA component.
107      * 
108      * @param descriptor The descriptor of the component to creat
109      * @param model the model in which the component is registered
110      */
111     public SedaComponent(MuleDescriptor descriptor, SedaModel model)
112     {
113         super(descriptor, model);
114 
115         descriptorQueueName = descriptor.getName() + ".component";
116         queueTimeout = model.getQueueTimeout();
117         enablePooling = model.isEnablePooling();
118         componentPerRequest = model.isComponentPerRequest();
119         poolingProfile = model.getPoolingProfile();
120         queueProfile = model.getQueueProfile();
121     }
122 
123     /**
124      * Initialise the component. The component will first create a Mule UMO from the
125      * UMODescriptor and then initialise a pool based on the attributes in the
126      * UMODescriptor.
127      * 
128      * @throws org.mule.umo.lifecycle.InitialisationException if the component fails
129      *             to initialise
130      * @see org.mule.umo.UMODescriptor
131      */
132     public synchronized void doInitialise() throws InitialisationException
133     {
134         // Create thread pool
135         ThreadingProfile tp = descriptor.getThreadingProfile();
136         workManager = tp.createWorkManager(descriptor.getName());
137 
138         queueProfile = descriptor.getQueueProfile();
139         if (queueProfile == null)
140         {
141             queueProfile = ((SedaModel) model).getQueueProfile();
142         }
143 
144         poolingProfile = descriptor.getPoolingProfile();
145         if (poolingProfile == null)
146         {
147             poolingProfile = ((SedaModel) model).getPoolingProfile();
148         }
149 
150         try
151         {
152             // Setup event Queue (used for VM execution)
153             queueProfile.configureQueue(descriptorQueueName);
154         }
155         catch (InitialisationException e)
156         {
157             throw e;
158         }
159         catch (Throwable e)
160         {
161             throw new InitialisationException(
162                 CoreMessages.objectFailedToInitialise("Component Queue"), e, this);
163         }
164     }
165 
166     protected ComponentStatistics createStatistics()
167     {
168         return new SedaComponentStatistics(getName(), descriptor.getThreadingProfile().getMaxThreadsActive(),
169             poolingProfile.getMaxActive());
170     }
171 
172     protected ObjectPool createPool() throws InitialisationException
173     {
174         return getPoolingProfile().getPoolFactory().createPool(descriptor, model,
175             getPoolingProfile());
176     }
177 
178     protected void initialisePool() throws InitialisationException
179     {
180         try
181         {
182             int initPolicy = getPoolingProfile().getInitialisationPolicy();
183             if (initPolicy == PoolingProfile.INITIALISE_ALL)
184             {
185                 int numToBorrow = getPoolingProfile().getMaxActive();
186                 List holderList = new ArrayList(numToBorrow);
187 
188                 try
189                 {
190                     for (int t = 0; t < numToBorrow; t++)
191                     {
192                         holderList.add(proxyPool.borrowObject());
193                     }
194                 }
195                 finally
196                 {
197                     for (int t = 0; t < holderList.size(); t++)
198                     {
199                         Object obj = holderList.get(t);
200                         if (obj != null)
201                         {
202                             try
203                             {
204                                 proxyPool.returnObject(obj);
205                             }
206                             finally
207                             {
208                                 // ignore - nothing we can do
209                             }
210                         }
211                     }
212                 }
213             }
214             else if (initPolicy == PoolingProfile.INITIALISE_ONE)
215             {
216                 Object obj = null;
217                 try
218                 {
219                     obj = proxyPool.borrowObject();
220                 }
221                 finally
222                 {
223                     if (obj != null)
224                     {
225                         proxyPool.returnObject(obj);
226                     }
227                 }
228             }
229 
230             poolInitialised.set(true);
231         }
232         catch (Exception e)
233         {
234             throw new InitialisationException(
235                 CoreMessages.objectFailedToInitialise("Proxy Pool"), e, this);
236         }
237     }
238 
239     protected MuleProxy createComponentProxy() throws InitialisationException
240     {
241         try
242         {
243             Object component = lookupComponent();
244             MuleProxy componentProxy = new DefaultMuleProxy(component, descriptor, model);
245             ((SedaComponentStatistics) getStatistics()).setComponentPoolSize(-1);
246             componentProxy.setStatistics(getStatistics());
247             componentProxy.start();
248             return componentProxy;
249         }
250         catch (UMOException e)
251         {
252             throw new InitialisationException(e, this);
253         }
254     }
255 
256     public void doForceStop() throws UMOException
257     {
258         doStop();
259     }
260 
261     public void doStop() throws UMOException
262     {
263         if (MuleManager.getInstance().getQueueManager().getQueueSession().getQueue(
264             descriptor.getName() + ".component").size() > 0)
265         {
266             try
267             {
268                 stopping.whenFalse(null);
269             }
270             catch (InterruptedException e)
271             {
272                 // we can ignore this
273                 // TODO MULE-863: Why?
274             }
275         }
276         workManager.stop();
277         if (proxyPool != null)
278         {
279             try
280             {
281                 proxyPool.stop();
282                 proxyPool.clearPool();
283             }
284             catch (Exception e)
285             {
286                 // TODO MULE-863: If this is an error, do something about it
287                 logger.error("Failed to stop component pool: " + e.getMessage(), e);
288             }
289             poolInitialised.set(false);
290         }
291         else if (componentProxy != null)
292         {
293             componentProxy.stop();
294         }
295     }
296 
297     public void doStart() throws UMOException
298     {
299 
300         try
301         {
302             // Need to initialise the pool only after all listerner have
303             // been registered and initialised so we need to delay until now
304             if (!poolInitialised.get() && enablePooling)
305             {
306                 proxyPool = this.createPool();
307                 this.initialisePool();
308                 proxyPool.start();
309             }
310             else if (!componentPerRequest)
311             {
312                 componentProxy = createComponentProxy();
313             }
314             workManager.start();
315             workManager.scheduleWork(this, WorkManager.INDEFINITE, null, this);
316         }
317         catch (Exception e)
318         {
319             throw new LifecycleException(
320                 CoreMessages.failedToStart("Component: " + descriptor.getName()), e, this);
321         }
322     }
323 
324     protected void doDispose()
325     {
326 
327         try
328         {
329             // threadPool.awaitTerminationAfterShutdown();
330             if (workManager != null)
331             {
332                 workManager.dispose();
333             }
334         }
335         catch (Exception e)
336         {
337             // TODO MULE-863: So what are we going to do about it?
338             logger.error("Component Thread Pool did not close properly: " + e);
339         }
340         try
341         {
342             if (proxyPool != null)
343             {
344                 proxyPool.clearPool();
345             }
346             else if (componentProxy != null)
347             {
348                 componentProxy.dispose();
349             }
350         }
351         catch (Exception e)
352         {
353             // TODO MULE-863: So what are we going to do about it?
354             logger.error("Proxy Pool did not close properly: " + e);
355         }
356     }
357 
358     protected void doDispatch(UMOEvent event) throws UMOException
359     {
360         // Dispatching event to the component
361         if (stats.isEnabled())
362         {
363             stats.incReceivedEventASync();
364         }
365         if (logger.isDebugEnabled())
366         {
367             logger.debug("Component: " + descriptor.getName() + " has received asynchronous event on: "
368                             + event.getEndpoint().getEndpointURI());
369         }
370 
371         // Block until we can queue the next event
372         try
373         {
374             enqueue(event);
375             if (stats.isEnabled())
376             {
377                 stats.incQueuedEvent();
378             }
379         }
380         catch (Exception e)
381         {
382             FailedToQueueEventException e1 = 
383                 new FailedToQueueEventException(
384                     CoreMessages.interruptedQueuingEventFor(this.getName()), 
385                     event.getMessage(), this, e);
386             handleException(e1);
387         }
388 
389         if (logger.isTraceEnabled())
390         {
391             logger.trace("Event added to queue for: " + descriptor.getName());
392         }
393     }
394 
395     public UMOMessage doSend(UMOEvent event) throws UMOException
396     {
397         UMOMessage result = null;
398         MuleProxy proxy = null;
399         try
400         {
401             proxy = getProxy();
402             if (logger.isDebugEnabled())
403             {
404                 logger.debug(this + " : got proxy for " + event.getId() + " = " + proxy);
405             }
406             result = (UMOMessage) proxy.onCall(event);
407         }
408         catch (UMOException e)
409         {
410             throw e;
411         }
412         catch (Exception e)
413         {
414             throw new ComponentException(event.getMessage(), this, e);
415         }
416         // Ensure that any proxy used for this request is released.
417         finally
418         {
419             releaseProxy(proxy);
420         }
421         return result;
422     }
423 
424     /**
425      * @return the pool of Mule UMOs initialised in this component
426      */
427     ObjectPool getProxyPool()
428     {
429         return proxyPool;
430     }
431 
432     public int getQueueSize()
433     {
434         QueueSession queueSession = MuleManager.getInstance().getQueueManager().getQueueSession();
435         return queueSession.getQueue(descriptor.getName()).size();
436     }
437 
438     /**
439      * While the component isn't stopped this runs a continuous loop checking for new
440      * events in the queue.
441      */
442     public void run()
443     {
444         MuleEvent event = null;
445         MuleProxy proxy = null;
446         QueueSession queueSession = MuleManager.getInstance().getQueueManager().getQueueSession();
447 
448         while (!stopped.get())
449         {
450             try
451             {
452                 // Wait if the component is paused
453                 paused.whenFalse(null);
454 
455                 // If we're doing a draining stop, read all events from the queue
456                 // before stopping
457                 if (stopping.get())
458                 {
459                     if (queueSession == null || queueSession.getQueue(descriptorQueueName).size() == 0)
460                     {
461                         stopping.set(false);
462                         break;
463                     }
464                 }
465 
466                 event = (MuleEvent) dequeue();
467                 if (event != null)
468                 {
469                     if (stats.isEnabled())
470                     {
471                         stats.decQueuedEvent();
472                     }
473 
474                     if (logger.isDebugEnabled())
475                     {
476                         logger.debug("Component: " + descriptor.getName() + " dequeued event on: "
477                                         + event.getEndpoint().getEndpointURI());
478                     }
479 
480                     proxy = getProxy();
481                     proxy.start();
482                     proxy.onEvent(queueSession, event);
483                     workManager.scheduleWork(proxy, WorkManager.INDEFINITE, null, this);
484                 }
485             }
486             catch (Exception e)
487             {
488                 // The proxy did not get created and/or schedule, so ensure it gets
489                 // released.
490                 releaseProxy(proxy);
491 
492                 if (e instanceof InterruptedException)
493                 {
494                     stopping.set(false);
495                     break;
496                 }
497                 else if (e instanceof NoSuchElementException)
498                 {
499                     handleException(new ComponentException(CoreMessages.proxyPoolTimedOut(),
500                         (event == null ? null : event.getMessage()), this, e));
501                 }
502                 else if (e instanceof UMOException)
503                 {
504                     handleException(e);
505                 }
506                 else if (e instanceof WorkException)
507                 {
508                     handleException(
509                         new ComponentException(
510                             CoreMessages.eventProcessingFailedFor(descriptor.getName()),
511                             (event == null ? null : event.getMessage()), this, e));
512                 }
513                 else
514                 {
515                     handleException(
516                         new ComponentException(
517                             CoreMessages.failedToGetPooledObject(),
518                             (event == null ? null : event.getMessage()), this, e));
519                 }
520             }
521             finally
522             {
523                 stopping.set(false);
524                 
525                 /* Removed: Since a componentPerRequest proxy is scheduled, it will be disposed by
526                  * {@link #workCompleted(WorkEvent)} or {@link #workRejected(WorkEvent)}}.
527                 if (proxy != null && componentPerRequest)
528                 {
529                     proxy.dispose();
530                 }
531                 */
532             }
533         }
534     }
535 
536     /**
537      * The proxy may be one of three types: 1. pooled 2. not pooled 3. per-request
538      */
539     protected MuleProxy getProxy() throws Exception
540     {
541         MuleProxy proxy;
542         if (proxyPool != null)
543         {
544             proxy = (MuleProxy) proxyPool.borrowObject();
545             ((SedaComponentStatistics) getStatistics()).setComponentPoolSize(proxyPool.getSize());
546         }
547         else if (componentPerRequest)
548         {
549             proxy = createComponentProxy();
550         }
551         else
552         {
553             proxy = componentProxy;
554         }
555         proxy.setStatistics(getStatistics());
556         return proxy;
557     }
558     
559     public void release()
560     {
561         stopping.set(false);
562     }
563 
564     protected void enqueue(UMOEvent event) throws Exception
565     {
566         QueueSession session = MuleManager.getInstance().getQueueManager().getQueueSession();
567         session.getQueue(descriptorQueueName).put(event);
568     }
569 
570     protected UMOEvent dequeue() throws Exception
571     {
572         // Wait until an event is available
573         QueueSession queueSession = MuleManager.getInstance().getQueueManager().getQueueSession();
574         return (UMOEvent) queueSession.getQueue(descriptorQueueName).poll(queueTimeout);
575     }
576 
577     public void workAccepted(WorkEvent event)
578     {
579         handleWorkException(event, "workAccepted");
580     }
581 
582     /**
583      * This method ensures that any component proxy associated with this rejected
584      * work is released.
585      *  
586      * @see #workCompleted(WorkEvent)
587      */
588     public void workRejected(WorkEvent event)
589     {
590         handleWorkException(event, "workRejected");
591 
592         if (event.getWork() instanceof MuleProxy) 
593         {
594             releaseProxy((MuleProxy) event.getWork());
595         }
596     }
597 
598     public void workStarted(WorkEvent event)
599     {
600         handleWorkException(event, "workStarted");
601     }
602 
603     /**
604      * There are two units of work that call this method when they complete
605      * (regardless of whether or not they incurred an exception):
606      * 1) This component's queue listener that processes asynchronous events by
607      *    scheduling a component proxy
608      * 2) Each scheduled component proxy
609      * <p>
610      * Generally, #1 occurs each time the component stops and #2 occurs
611      * at the end of each asynchronous event.
612      * <p>
613      * This method is responsible for handling any exceptions that occur for both
614      * #1 and #2 and releasing the proxy from #2. 
615      *    
616      * @see WorkListener#workCompleted(WorkEvent)
617      */
618     public void workCompleted(WorkEvent event)
619     {
620         handleWorkException(event, "workCompleted");
621 
622         if (event.getWork() instanceof MuleProxy) 
623         {
624             releaseProxy((MuleProxy) event.getWork());
625         }
626     }
627 
628     protected void handleWorkException(WorkEvent event, String type)
629     {
630         Throwable e;
631 
632         if (event != null && event.getException() != null)
633         {
634             e = event.getException();
635         }
636         else
637         {
638             return;
639         }
640 
641         if (event.getException().getCause() != null)
642         {
643             e = event.getException().getCause();
644         }
645 
646         logger.error("Work caused exception on '" + type + "'. Work being executed was: "
647                         + event.getWork().toString());
648 
649         if (e instanceof Exception)
650         {
651             handleException((Exception) e);
652         }
653         else
654         {
655             throw new MuleRuntimeException(
656                 CoreMessages.componentCausedErrorIs(this.getName()), e);
657         }
658     }
659 
660     public PoolingProfile getPoolingProfile()
661     {
662         return poolingProfile;
663     }
664 
665     public void setPoolingProfile(PoolingProfile poolingProfile)
666     {
667         this.poolingProfile = poolingProfile;
668     }
669 
670     public QueueProfile getQueueProfile()
671     {
672         return queueProfile;
673     }
674 
675     public void setQueueProfile(QueueProfile queueProfile)
676     {
677         this.queueProfile = queueProfile;
678     }
679 
680     /**
681      * This is a helper method that catches any exceptions that occur while
682      * releasing a component proxy and reports them to the component for
683      * handling.
684      * 
685      * @param proxy -
686      *            the proxy that has completed; may be null in which case
687      *            nothing occurs
688      * 
689      * @see #doReleaseProxy(MuleProxy)
690      */
691     private void releaseProxy(MuleProxy proxy) 
692     {
693         if (proxy == null) 
694         {
695             return;
696         }
697         
698         try 
699         {
700             doReleaseProxy(proxy);
701         } 
702         catch (Exception ex)
703         {
704             handleException(ex);
705         }
706         // else, keep the singleton proxy for future use.
707     }
708     
709     /**
710      * This method is called once when a proxy has finished processing a request
711      * (regardless of success). For every call to
712      * {@link #getProxy()), there will be a corresponding call to this method. 
713      * The default behavior is to return the proxy to the pool, dispose of it,
714      * or reuse it, depending on the configuration.
715      * <p>
716      * <b>NOTE:</b> The implementation of this method must be thread-safe.
717      * 
718      * @param proxy -
719      *            the proxy that has completed; must be non-null.
720      * 
721      * @throws Exception -
722      *             the proxy cannot be released.
723      */
724     protected void doReleaseProxy(MuleProxy proxy) throws Exception
725     {
726         assert (proxy != null);
727 
728         if (proxyPool != null) 
729         {
730             proxyPool.returnObject(proxy);
731             ((SedaComponentStatistics) getStatistics()).setComponentPoolSize(proxyPool.getSize());
732         } 
733         else if (componentPerRequest) 
734         {
735             proxy.dispose();
736         }
737         // else, keep the singleton proxy for future use.
738     }
739 }