View Javadoc

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