View Javadoc

1   /*
2    * $Id: AbstractExceptionListener.java 12370 2008-07-17 13:11:17Z tcarlson $
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;
12  
13  import org.mule.api.MessagingException;
14  import org.mule.api.MuleContext;
15  import org.mule.api.MuleEvent;
16  import org.mule.api.MuleEventContext;
17  import org.mule.api.MuleException;
18  import org.mule.api.MuleMessage;
19  import org.mule.api.config.MuleProperties;
20  import org.mule.api.context.MuleContextAware;
21  import org.mule.api.endpoint.EndpointURI;
22  import org.mule.api.endpoint.ImmutableEndpoint;
23  import org.mule.api.endpoint.InvalidEndpointTypeException;
24  import org.mule.api.endpoint.OutboundEndpoint;
25  import org.mule.api.lifecycle.Disposable;
26  import org.mule.api.lifecycle.Initialisable;
27  import org.mule.api.lifecycle.InitialisationException;
28  import org.mule.api.lifecycle.LifecycleException;
29  import org.mule.api.routing.RoutingException;
30  import org.mule.api.transaction.Transaction;
31  import org.mule.api.transaction.TransactionException;
32  import org.mule.api.util.StreamCloserService;
33  import org.mule.config.ExceptionHelper;
34  import org.mule.config.i18n.CoreMessages;
35  import org.mule.context.notification.ExceptionNotification;
36  import org.mule.message.ExceptionMessage;
37  import org.mule.routing.filters.WildcardFilter;
38  import org.mule.transaction.TransactionCoordination;
39  import org.mule.transport.NullPayload;
40  import org.mule.util.CollectionUtils;
41  
42  import java.beans.ExceptionListener;
43  import java.util.Iterator;
44  import java.util.List;
45  
46  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
47  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
48  
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  
52  /**
53   * <code>AbstractExceptionListener</code> is a base implementation that custom
54   * Exception Listeners can override. It provides template methods for handling the
55   * for base types of exceptions plus allows multimple endpoints to be associated with
56   * this exception listener and provides an implementation for dispatching exception
57   * events from this Listener.
58   */
59  public abstract class AbstractExceptionListener implements ExceptionListener, Initialisable, Disposable, MuleContextAware
60  {
61      /**
62       * logger used by this class
63       */
64      protected transient Log logger = LogFactory.getLog(getClass());
65  
66      protected List endpoints = new CopyOnWriteArrayList();
67  
68      protected AtomicBoolean initialised = new AtomicBoolean(false);
69  
70      protected MuleContext muleContext;
71  
72      protected WildcardFilter rollbackTxFilter;
73      protected WildcardFilter commitTxFilter;
74  
75      protected boolean enableNotifications = true;
76  
77      public void setMuleContext(MuleContext context)
78      {
79          this.muleContext = context;
80      }
81  
82      public List getEndpoints()
83      {
84          return endpoints;
85      }
86  
87      public void setEndpoints(List endpoints)
88      {
89          if (endpoints != null)
90          {
91              this.endpoints.clear();
92              // Ensure all endpoints are outbound endpoints
93              // This will go when we start dropping suport for 1.4 and start using 1.5
94              for (Iterator it = endpoints.iterator(); it.hasNext();)
95              {
96                  ImmutableEndpoint endpoint = (ImmutableEndpoint) it.next();
97                  if (!(endpoint instanceof OutboundEndpoint))
98                  {
99                      throw new InvalidEndpointTypeException(CoreMessages.exceptionListenerMustUseOutboundEndpoint(this,
100                         endpoint));
101                 }
102             }
103             this.endpoints.addAll(endpoints);
104         }
105         else
106         {
107             throw new IllegalArgumentException("List of endpoints = null");
108         }
109     }
110 
111     public void addEndpoint(OutboundEndpoint endpoint)
112     {
113         if (endpoint != null)
114         {
115             endpoints.add(endpoint);
116         }
117     }
118 
119     public boolean removeEndpoint(OutboundEndpoint endpoint)
120     {
121         return endpoints.remove(endpoint);
122     }
123 
124     public void exceptionThrown(Exception e)
125     {
126         if (enableNotifications)
127         {
128             fireNotification(new ExceptionNotification(e));
129         }
130 
131         logException(e);
132         handleTransaction(e);
133 
134         Throwable t = getExceptionType(e, RoutingException.class);
135         if (t != null)
136         {
137             RoutingException re = (RoutingException) t;
138             handleRoutingException(re.getUmoMessage(), re.getEndpoint(), e);
139             return;
140         }
141 
142         t = getExceptionType(e, MessagingException.class);
143         if (t != null)
144         {
145             MessagingException me = (MessagingException) t;
146             handleMessagingException(me.getUmoMessage(), e);
147             return;
148         }
149 
150         t = getExceptionType(e, LifecycleException.class);
151         if (t != null)
152         {
153             LifecycleException le = (LifecycleException) t;
154             handleLifecycleException(le.getComponent(), e);
155             if (RequestContext.getEventContext() != null)
156             {
157                 handleMessagingException(RequestContext.getEventContext().getMessage(), e);
158             }
159             else
160             {
161                 logger.info("There is no current event available, routing Null message with the exception");
162                 handleMessagingException(new DefaultMuleMessage(NullPayload.getInstance()), e);
163             }
164             return;
165         }
166 
167         handleStandardException(e);
168     }
169 
170     protected Throwable getExceptionType(Throwable t, Class exceptionType)
171     {
172         while (t != null)
173         {
174             if (exceptionType.isAssignableFrom(t.getClass()))
175             {
176                 return t;
177             }
178 
179             t = t.getCause();
180         }
181 
182         return null;
183     }
184 
185     /**
186      * The initialise method is call every time the Exception stategy is assigned to
187      * a service or connector. This implementation ensures that initialise is
188      * called only once. The actual initialisation code is contained in the
189      * <code>doInitialise()</code> method.
190      *
191      * @throws InitialisationException
192      */
193     public final synchronized void initialise() throws InitialisationException
194     {
195         if (!initialised.get())
196         {
197             doInitialise(muleContext);
198             initialised.set(true);
199         }
200     }
201 
202     protected void doInitialise(MuleContext muleContext) throws InitialisationException
203     {
204         logger.info("Initialising exception listener: " + toString());
205     }
206 
207     /**
208      * If there is a current transaction this method will mark it for rollback This
209      * method should not be called if an event is routed from this exception handler
210      * to an endpoint that should take part in the current transaction
211      */
212     protected void handleTransaction(Throwable t)
213     {
214         Transaction tx = TransactionCoordination.getInstance().getTransaction();
215 
216         if (tx == null)
217         {
218             return;
219         }
220         //Work with the root exception, not anything thaat wraps it
221         t = ExceptionHelper.getRootException(t);
222 
223         if (rollbackTxFilter == null && commitTxFilter == null)
224         {
225             //By default, rollback the transaction
226             rollbackTransaction();
227         }
228         else if (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
229         {
230             //the rollback filter take preceedence over th ecommit filter
231             rollbackTransaction();
232         }
233         else if (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()))
234         {
235             //we only have to rollback if the commitTxFilter does NOT match
236             rollbackTransaction();
237         }
238     }
239 
240     protected void rollbackTransaction()
241     {
242         Transaction tx = TransactionCoordination.getInstance().getTransaction();
243         try
244         {
245             if (tx != null)
246             {
247                 tx.setRollbackOnly();
248             }
249         }
250         catch (TransactionException e)
251         {
252             logException(e);
253         }
254     }
255 
256     /**
257      * Routes the current exception to an error endpoint such as a Dead Letter Queue
258      * (jms) This method is only invoked if there is a MuleMessage available to
259      * dispatch. The message dispatched from this method will be an
260      * <code>ExceptionMessage</code> which contains the exception thrown the
261      * MuleMessage and any context information.
262      *
263      * @param message        the MuleMessage being processed when the exception occurred
264      * @param failedEndpoint optional; the endpoint being dispatched or received on
265      *                       when the error occurred. This is NOT the endpoint that the message
266      *                       will be disptched on and is only supplied to this method for
267      *                       logging purposes
268      * @param t              the exception thrown. This will be sent with the ExceptionMessage
269      * @see ExceptionMessage
270      */
271     protected void routeException(MuleMessage message, ImmutableEndpoint failedEndpoint, Throwable t)
272     {
273         List endpoints = getEndpoints(t);
274         if (CollectionUtils.isNotEmpty(endpoints))
275         {
276             try
277             {
278                 logger.error("Message being processed is: " + (message == null ? "null" : message.toString()));
279                 MuleEventContext ctx = RequestContext.getEventContext();
280                 String component = "Unknown";
281                 EndpointURI endpointUri = null;
282                 if (ctx != null)
283                 {
284                     if (ctx.getService() != null)
285                     {
286                         component = ctx.getService().getName();
287                     }
288                     endpointUri = ctx.getEndpointURI();
289                 }
290                 else if (failedEndpoint != null)
291                 {
292                     endpointUri = failedEndpoint.getEndpointURI();
293                 }
294                 ExceptionMessage msg;
295                 msg = new ExceptionMessage(getErrorMessagePayload(message), t, component, endpointUri);
296 
297                 MuleMessage exceptionMessage;
298                 if (ctx == null)
299                 {
300                     exceptionMessage = new DefaultMuleMessage(msg);
301                 }
302                 else
303                 {
304                     exceptionMessage = new DefaultMuleMessage(msg, ctx.getMessage());
305                 }
306 
307                 for (int i = 0; i < endpoints.size(); i++)
308                 {
309                     OutboundEndpoint endpoint = (OutboundEndpoint) endpoints.get(i);
310                     MuleEvent exceptionEvent = new DefaultMuleEvent(exceptionMessage, endpoint, new DefaultMuleSession(
311                         exceptionMessage, new MuleSessionHandler(), muleContext), true);
312                     exceptionEvent = RequestContext.setEvent(exceptionEvent);
313                     endpoint.send(exceptionEvent);
314 
315                     if (logger.isDebugEnabled())
316                     {
317                         logger.debug("routed Exception message via " + endpoint);
318                     }
319                 }
320             }
321             catch (MuleException e)
322             {
323                 logFatal(message, e);
324                 closeStream(message);
325             }
326         }
327         else
328         {
329             handleTransaction(t);
330             closeStream(message);
331         }
332     }
333 
334     protected void closeStream(MuleMessage message)
335     {
336         if (muleContext != null)
337         {
338             ((StreamCloserService) muleContext.getRegistry().lookupObject(
339                 MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE)).closeStream(message.getPayload());
340         }
341     }
342 
343     protected Object getErrorMessagePayload(MuleMessage message)
344     {
345         try
346         {
347             return message.getPayloadAsString();
348         }
349         catch (Exception e)
350         {
351             logException(e);
352             logger.info("Failed to read message payload as string, using raw payload");
353             return message.getPayload();
354         }
355     }
356 
357     /**
358      * Returns an endpoint for the given exception. ExceptionListeners can have
359      * multiple endpoints registered on them. This methods allows custom
360      * implementations to control which endpoint is used based on the exception
361      * thrown. This implementation simply returns the first endpoint in the list.
362      *
363      * @param t the exception thrown
364      * @return The endpoint used to dispatch an exception message on or null if there
365      *         are no endpoints registered
366      */
367     protected List getEndpoints(Throwable t)
368     {
369         if (!endpoints.isEmpty())
370         {
371             return endpoints;
372         }
373         else
374         {
375             return null;
376         }
377     }
378 
379     /**
380      * Used to log the error passed into this Exception Listener
381      *
382      * @param t the exception thrown
383      */
384     protected void logException(Throwable t)
385     {
386         MuleException umoe = ExceptionHelper.getRootMuleException(t);
387         if (umoe != null)
388         {
389             logger.error(umoe.getDetailedMessage());
390         }
391         else
392         {
393             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
394         }
395     }
396 
397     /**
398      * Logs a fatal error message to the logging system. This should be used mostly
399      * if an error occurs in the exception listener itself. This implementation logs
400      * the the message itself to the logs if it is not null
401      *
402      * @param message The MuleMessage currently being processed
403      * @param t       the fatal exception to log
404      */
405     protected void logFatal(MuleMessage message, Throwable t)
406     {
407         logger.fatal(
408                 "Failed to dispatch message to error queue after it failed to process.  This may cause message loss."
409                         + (message == null ? "" : "Logging Message here: \n" + message.toString()), t);
410     }
411 
412     public boolean isInitialised()
413     {
414         return initialised.get();
415     }
416 
417 
418     public void dispose()
419     {
420         // Template method
421     }
422 
423     /**
424      * Fires a server notification to all registered
425      * {@link org.mule.api.context.notification.listener.ExceptionNotificationListener}
426      * eventManager.
427      *
428      * @param notification the notification to fire.
429      */
430     protected void fireNotification(ExceptionNotification notification)
431     {
432         if (muleContext != null)
433         {
434             muleContext.fireNotification(notification);
435         }
436         else if (logger.isWarnEnabled())
437         {
438             logger.debug("MuleContext is not yet available for firing notifications, ignoring event: " + notification);
439         }
440     }
441 
442     public WildcardFilter getCommitTxFilter()
443     {
444         return commitTxFilter;
445     }
446 
447     public void setCommitTxFilter(WildcardFilter commitTxFilter)
448     {
449         this.commitTxFilter = commitTxFilter;
450     }
451 
452     public boolean isEnableNotifications()
453     {
454         return enableNotifications;
455     }
456 
457     public void setEnableNotifications(boolean enableNotifications)
458     {
459         this.enableNotifications = enableNotifications;
460     }
461 
462     public WildcardFilter getRollbackTxFilter()
463     {
464         return rollbackTxFilter;
465     }
466 
467     public void setRollbackTxFilter(WildcardFilter rollbackTxFilter)
468     {
469         this.rollbackTxFilter = rollbackTxFilter;
470     }
471 
472     /**
473      * A messaging exception is thrown when an excpetion occurs during normal message
474      * processing. A <code>MessagingException</code> holds a reference to the
475      * current message that is passed into this method
476      *
477      * @param message the current message being processed
478      * @param e       the top level exception thrown. This may be a Messaging exception or
479      *                some wrapper exception
480      * @see MessagingException
481      */
482     public abstract void handleMessagingException(MuleMessage message, Throwable e);
483 
484     /**
485      * A routing exception is thrown when an excpetion occurs during normal message
486      * processing A <code>RoutingException</code> holds a reference to the current
487      * message and te endpoint being routing to or from when the error occurred. Both
488      * are passed into this method
489      *
490      * @param message  the current message being processed
491      * @param endpoint the endpoint being dispatched to or received from when the
492      *                 error occurred
493      * @param e        the top level exception thrown. This may be a Messaging exception or
494      *                 some wrapper exception
495      * @see RoutingException
496      */
497     public abstract void handleRoutingException(MuleMessage message, ImmutableEndpoint endpoint, Throwable e);
498 
499     /**
500      * DefaultLifecyclePhase exceptions are thrown when an error occurs during an object's
501      * lifecycle call such as start, stop or initialise. The exception contains a
502      * reference to the object that failed which can be used for more informative
503      * logging.
504      *
505      * @param service the object that failed during a lifecycle call
506      * @param e       the top level exception thrown. This may or may not be the
507      *                <code>LifecycleException</code> but a lifecycle exception will be
508      *                present in the exception stack.
509      * @see LifecycleException
510      */
511     public abstract void handleLifecycleException(Object component, Throwable e);
512 
513     /**
514      * A handler for all other exceptions
515      *
516      * @param e the top level exception thrown
517      */
518     public abstract void handleStandardException(Throwable e);
519 
520 
521 }