View Javadoc

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