View Javadoc

1   /*
2    * $Id: AbstractExceptionListener.java 8597 2007-09-26 09:23:59Z 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;
12  
13  import org.mule.MuleManager;
14  import org.mule.config.ExceptionHelper;
15  import org.mule.impl.internal.notifications.ExceptionNotification;
16  import org.mule.impl.message.ExceptionMessage;
17  import org.mule.providers.NullPayload;
18  import org.mule.transaction.TransactionCoordination;
19  import org.mule.umo.MessagingException;
20  import org.mule.umo.TransactionException;
21  import org.mule.umo.UMOEvent;
22  import org.mule.umo.UMOEventContext;
23  import org.mule.umo.UMOException;
24  import org.mule.umo.UMOMessage;
25  import org.mule.umo.UMOTransaction;
26  import org.mule.umo.endpoint.UMOEndpoint;
27  import org.mule.umo.endpoint.UMOEndpointURI;
28  import org.mule.umo.endpoint.UMOImmutableEndpoint;
29  import org.mule.umo.lifecycle.Initialisable;
30  import org.mule.umo.lifecycle.InitialisationException;
31  import org.mule.umo.lifecycle.LifecycleException;
32  import org.mule.umo.routing.RoutingException;
33  
34  import java.beans.ExceptionListener;
35  import java.util.Iterator;
36  import java.util.List;
37  
38  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
39  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  /**
44   * <code>AbstractExceptionListener</code> is a base implementation that custom
45   * Exception Listeners can override. It provides template methods for handling the
46   * for base types of exceptions plus allows multimple endpoints to be associated with
47   * this exception listener and provides an implementation for dispatching exception
48   * events from this Listener.
49   */
50  public abstract class AbstractExceptionListener implements ExceptionListener, Initialisable
51  {
52      /**
53       * logger used by this class
54       */
55      protected transient Log logger = LogFactory.getLog(getClass());
56  
57      protected List endpoints = new CopyOnWriteArrayList();
58  
59      protected AtomicBoolean initialised = new AtomicBoolean(false);
60  
61      public List getEndpoints()
62      {
63          return endpoints;
64      }
65  
66      public void setEndpoints(List endpoints)
67      {
68          for (Iterator iterator = endpoints.iterator(); iterator.hasNext();)
69          {
70              addEndpoint((UMOEndpoint) iterator.next());
71          }
72      }
73  
74      public void addEndpoint(UMOEndpoint endpoint)
75      {
76          if (endpoint != null)
77          {
78              endpoint.setType(UMOEndpoint.ENDPOINT_TYPE_SENDER);
79              endpoints.add(endpoint);
80          }
81      }
82  
83      public boolean removeEndpoint(UMOEndpoint endpoint)
84      {
85          return endpoints.remove(endpoint);
86      }
87  
88      public void exceptionThrown(Exception e)
89      {
90          fireNotification(new ExceptionNotification(e));
91          logException(e);
92  
93          Throwable t = getExceptionType(e, RoutingException.class);
94          if (t != null)
95          {
96              RoutingException re = (RoutingException) t;
97              handleRoutingException(re.getUmoMessage(), re.getEndpoint(), e);
98              return;
99          }
100 
101         t = getExceptionType(e, MessagingException.class);
102         if (t != null)
103         {
104             MessagingException me = (MessagingException) t;
105             handleMessagingException(me.getUmoMessage(), e);
106             return;
107         }
108 
109         t = getExceptionType(e, LifecycleException.class);
110         if (t != null)
111         {
112             LifecycleException le = (LifecycleException) t;
113             handleLifecycleException(le.getComponent(), e);
114             if (RequestContext.getEventContext() != null)
115             {
116                 handleMessagingException(RequestContext.getEventContext().getMessage(), e);
117             }
118             else
119             {
120                 logger.info("There is no current event available, routing Null message with the exception");
121                 handleMessagingException(new MuleMessage(NullPayload.getInstance()), e);
122             }
123             return;
124         }
125 
126         handleStandardException(e);
127     }
128 
129     protected Throwable getExceptionType(Throwable t, Class exceptionType)
130     {
131         while (t != null)
132         {
133             if (exceptionType.isAssignableFrom(t.getClass()))
134             {
135                 return t;
136             }
137 
138             t = t.getCause();
139         }
140 
141         return null;
142     }
143 
144     /**
145      * The initialise method is call every time the Exception stategy is assigned to
146      * a component or connector. This implementation ensures that initialise is
147      * called only once. The actual initialisation code is contained in the
148      * <code>doInitialise()</code> method.
149      * 
150      * @throws InitialisationException
151      */
152     public final synchronized void initialise() throws InitialisationException
153     {
154         if (!initialised.get())
155         {
156             doInitialise();
157             initialised.set(true);
158         }
159     }
160 
161     protected void doInitialise() throws InitialisationException
162     {
163         logger.info("Initialising exception listener: " + toString());
164         for (Iterator iterator = endpoints.iterator(); iterator.hasNext();)
165         {
166             UMOEndpoint umoEndpoint = (UMOEndpoint) iterator.next();
167             umoEndpoint.initialise();
168         }
169     }
170 
171     /**
172      * If there is a current transaction this method will mark it for rollback This
173      * method should not be called if an event is routed from this exception handler
174      * to an endpoint that should take part in the current transaction
175      */
176     protected void markTransactionForRollback()
177     {
178         UMOTransaction tx = TransactionCoordination.getInstance().getTransaction();
179         try
180         {
181             if (tx != null)
182             {
183                 tx.setRollbackOnly();
184             }
185         }
186         catch (TransactionException e)
187         {
188             logException(e);
189         }
190     }
191 
192     /**
193      * Routes the current exception to an error endpoint such as a Dead Letter Queue
194      * (jms) This method is only invoked if there is a UMOMessage available to
195      * dispatch. The message dispatched from this method will be an
196      * <code>ExceptionMessage</code> which contains the exception thrown the
197      * UMOMessage and any context information.
198      * 
199      * @param message the UMOMessage being processed when the exception occurred
200      * @param failedEndpoint optional; the endpoint being dispatched or received on
201      *            when the error occurred. This is NOT the endpoint that the message
202      *            will be disptched on and is only supplied to this method for
203      *            logging purposes
204      * @param t the exception thrown. This will be sent with the ExceptionMessage
205      * @see ExceptionMessage
206      */
207     protected void routeException(UMOMessage message, UMOImmutableEndpoint failedEndpoint, Throwable t)
208     {
209         UMOEndpoint endpoint = getEndpoint(t);
210         if (endpoint != null)
211         {
212             try
213             {
214                 logger.error("Message being processed is: " + (message == null ? "null" : message.toString()));
215                 UMOEventContext ctx = RequestContext.getEventContext();
216                 String component = "Unknown";
217                 UMOEndpointURI endpointUri = null;
218                 if (ctx != null)
219                 {
220                     if (ctx.getComponentDescriptor() != null)
221                     {
222                         component = ctx.getComponentDescriptor().getName();
223                     }
224                     endpointUri = ctx.getEndpointURI();
225                 }
226                 else if (failedEndpoint != null)
227                 {
228                     endpointUri = failedEndpoint.getEndpointURI();
229                 }
230                 ExceptionMessage msg;
231                 msg = new ExceptionMessage(getErrorMessagePayload(message), t, component, endpointUri);
232 
233                 UMOMessage exceptionMessage;
234                 if (ctx == null)
235                 {
236                     exceptionMessage = new MuleMessage(msg);
237                 }
238                 else
239                 {
240                     exceptionMessage = new MuleMessage(msg, ctx.getMessage());
241                 }
242                 UMOEvent exceptionEvent = new MuleEvent(exceptionMessage, endpoint, new MuleSession(
243                     exceptionMessage, new MuleSessionHandler()), true);
244                 exceptionEvent = RequestContext.setEvent(exceptionEvent);
245                 endpoint.send(exceptionEvent);
246 
247                 if (logger.isDebugEnabled())
248                 {
249                     logger.debug("routed Exception message via " + endpoint);
250                 }
251 
252             }
253             catch (UMOException e)
254             {
255                 logFatal(message, e);
256             }
257         }
258         else
259         {
260             markTransactionForRollback();
261         }
262     }
263 
264     protected Object getErrorMessagePayload(UMOMessage message)
265     {
266         try
267         {
268             return message.getPayloadAsString();
269         }
270         catch (Exception e)
271         {
272             logException(e);
273             logger.info("Failed to read message payload as string, using raw payload");
274             return message.getPayload();
275         }
276     }
277 
278     /**
279      * Returns an endpoint for the given exception. ExceptionListeners can have
280      * multiple endpoints registered on them. This methods allows custom
281      * implementations to control which endpoint is used based on the exception
282      * thrown. This implementation simply returns the first endpoint in the list.
283      * 
284      * @param t the exception thrown
285      * @return The endpoint used to dispatch an exception message on or null if there
286      *         are no endpoints registered
287      */
288     protected UMOEndpoint getEndpoint(Throwable t)
289     {
290         if (endpoints.size() > 0)
291         {
292             return (UMOEndpoint) endpoints.get(0);
293         }
294         else
295         {
296             return null;
297         }
298     }
299 
300     /**
301      * Used to log the error passed into this Exception Listener
302      * 
303      * @param t the exception thrown
304      */
305     protected void logException(Throwable t)
306     {
307         UMOException umoe = ExceptionHelper.getRootMuleException(t);
308         if (umoe != null)
309         {
310             logger.error(umoe.getDetailedMessage());
311         }
312         else
313         {
314             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
315         }
316     }
317 
318     /**
319      * Logs a fatal error message to the logging system. This should be used mostly
320      * if an error occurs in the exception listener itself. This implementation logs
321      * the the message itself to the logs if it is not null
322      * 
323      * @param message The UMOMessage currently being processed
324      * @param t the fatal exception to log
325      */
326     protected void logFatal(UMOMessage message, Throwable t)
327     {
328         logger.fatal(
329             "Failed to dispatch message to error queue after it failed to process.  This may cause message loss."
330                             + (message == null ? "" : "Logging Message here: \n" + message.toString()), t);
331     }
332 
333     public boolean isInitialised()
334     {
335         return initialised.get();
336     }
337 
338     /**
339      * Fires a server notification to all registered
340      * {@link org.mule.impl.internal.notifications.ExceptionNotificationListener}
341      * eventManager.
342      *
343      * @param notification the notification to fire.
344      */
345     protected void fireNotification(ExceptionNotification notification)
346     {
347         MuleManager.getInstance().fireNotification(notification);
348     }
349 
350     /**
351      * A messaging exception is thrown when an excpetion occurs during normal message
352      * processing. A <code>MessagingException</code> holds a reference to the
353      * current message that is passed into this method
354      * 
355      * @param message the current message being processed
356      * @param e the top level exception thrown. This may be a Messaging exception or
357      *            some wrapper exception
358      * @see MessagingException
359      */
360     public abstract void handleMessagingException(UMOMessage message, Throwable e);
361 
362     /**
363      * A routing exception is thrown when an excpetion occurs during normal message
364      * processing A <code>RoutingException</code> holds a reference to the current
365      * message and te endpoint being routing to or from when the error occurred. Both
366      * are passed into this method
367      * 
368      * @param message the current message being processed
369      * @param endpoint the endpoint being dispatched to or received from when the
370      *            error occurred
371      * @param e the top level exception thrown. This may be a Messaging exception or
372      *            some wrapper exception
373      * @see RoutingException
374      */
375     public abstract void handleRoutingException(UMOMessage message, UMOImmutableEndpoint endpoint, Throwable e);
376 
377     /**
378      * Lifecycle exceptions are thrown when an error occurs during an object's
379      * lifecycle call such as start, stop or initialise. The exception contains a
380      * reference to the object that failed which can be used for more informative
381      * logging.
382      * 
383      * @param component the object that failed during a lifecycle call
384      * @param e the top level exception thrown. This may or may not be the
385      *            <code>LifecycleException</code> but a lifecycle exception will be
386      *            present in the exception stack.
387      * @see LifecycleException
388      */
389     public abstract void handleLifecycleException(Object component, Throwable e);
390 
391     /**
392      * A handler for all other exceptions
393      * 
394      * @param e the top level exception thrown
395      */
396     public abstract void handleStandardException(Throwable e);
397 }