View Javadoc

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