View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.exception;
8   
9   import org.mule.DefaultMuleEvent;
10  import org.mule.DefaultMuleMessage;
11  import org.mule.api.MuleContext;
12  import org.mule.api.MuleEvent;
13  import org.mule.api.MuleException;
14  import org.mule.api.MuleMessage;
15  import org.mule.api.config.MuleProperties;
16  import org.mule.api.construct.FlowConstruct;
17  import org.mule.api.endpoint.EndpointURI;
18  import org.mule.api.lifecycle.InitialisationException;
19  import org.mule.api.processor.MessageProcessor;
20  import org.mule.api.transaction.Transaction;
21  import org.mule.api.transaction.TransactionException;
22  import org.mule.api.util.StreamCloserService;
23  import org.mule.config.ExceptionHelper;
24  import org.mule.context.notification.ExceptionNotification;
25  import org.mule.message.ExceptionMessage;
26  import org.mule.processor.AbstractMessageProcessorOwner;
27  import org.mule.routing.filters.WildcardFilter;
28  import org.mule.routing.outbound.MulticastingRouter;
29  import org.mule.transaction.TransactionCoordination;
30  
31  import java.util.List;
32  import java.util.concurrent.CopyOnWriteArrayList;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  /**
39   * <code>AbstractExceptionListener</code> is a base implementation that custom
40   * Exception Listeners can override. It provides template methods for handling the
41   * for base types of exceptions plus allows multiple targets to be associated with
42   * this exception listener and provides an implementation for dispatching exception
43   * events from this Listener.
44   */
45  public abstract class AbstractExceptionListener extends AbstractMessageProcessorOwner
46  {
47      /**
48       * logger used by this class
49       */
50      protected transient Log logger = LogFactory.getLog(getClass());
51  
52      @SuppressWarnings("unchecked")
53      protected List<MessageProcessor> messageProcessors = new CopyOnWriteArrayList();
54  
55      protected AtomicBoolean initialised = new AtomicBoolean(false);
56  
57      protected WildcardFilter rollbackTxFilter;
58      protected WildcardFilter commitTxFilter;
59  
60      protected boolean enableNotifications = true;
61  
62      public List<MessageProcessor> getMessageProcessors()
63      {
64          return messageProcessors;
65      }
66  
67      public void setMessageProcessors(List<MessageProcessor> processors)
68      {
69          if (processors != null)
70          {
71              this.messageProcessors.clear();
72              this.messageProcessors.addAll(processors);
73          }
74          else
75          {
76              throw new IllegalArgumentException("List of targets = null");
77          }
78      }
79  
80      public void addEndpoint(MessageProcessor processor)
81      {
82          if (processor != null)
83          {
84              messageProcessors.add(processor);
85          }
86      }
87  
88      public boolean removeMessageProcessor(MessageProcessor processor)
89      {
90          return messageProcessors.remove(processor);
91      }
92  
93      protected Throwable getExceptionType(Throwable t, Class exceptionType)
94      {
95          while (t != null)
96          {
97              if (exceptionType.isAssignableFrom(t.getClass()))
98              {
99                  return t;
100             }
101 
102             t = t.getCause();
103         }
104 
105         return null;
106     }
107 
108     /**
109      * The initialise method is call every time the Exception stategy is assigned to
110      * a service or connector. This implementation ensures that initialise is called
111      * only once. The actual initialisation code is contained in the
112      * <code>doInitialise()</code> method.
113      * 
114      * @throws InitialisationException
115      */
116     public final synchronized void initialise() throws InitialisationException
117     {
118         super.initialise();
119         if (!initialised.get())
120         {
121             doInitialise(muleContext);
122             initialised.set(true);
123         }
124     }
125 
126     protected void doInitialise(MuleContext muleContext) throws InitialisationException
127     {
128         logger.info("Initialising exception listener: " + toString());
129     }
130 
131     /**
132      * If there is a current transaction this method will mark it for rollback This
133      * method should not be called if an event is routed from this exception handler
134      * to an endpoint that should take part in the current transaction
135      */
136     protected void handleTransaction(Throwable t)
137     {
138         Transaction tx = TransactionCoordination.getInstance().getTransaction();
139 
140         if (tx == null)
141         {
142             return;
143         }
144         // Work with the root exception, not anything thaat wraps it
145         t = ExceptionHelper.getRootException(t);
146 
147         if (rollbackTxFilter == null && commitTxFilter == null)
148         {
149             // By default, rollback the transaction
150             rollbackTransaction();
151         }
152         else if (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
153         {
154             // the rollback filter take preceedence over th ecommit filter
155             rollbackTransaction();
156         }
157         else if (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()))
158         {
159             // we only have to rollback if the commitTxFilter does NOT match
160             rollbackTransaction();
161         }
162     }
163 
164     protected boolean isRollback(Throwable t)
165     {
166         // Work with the root exception, not anything thaat wraps it
167         t = ExceptionHelper.getRootException(t);
168         if (rollbackTxFilter == null && commitTxFilter == null)
169         {
170             return true;
171         }
172         else
173         {
174             return (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
175                    || (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()));
176         }
177     }
178 
179     protected void rollbackTransaction()
180     {
181         Transaction tx = TransactionCoordination.getInstance().getTransaction();
182         try
183         {
184             if (tx != null)
185             {
186                 tx.rollback();
187             }
188         }
189         catch (TransactionException e)
190         {
191             logException(e);
192         }
193     }
194 
195     /**
196      * Routes the current exception to an error endpoint such as a Dead Letter Queue
197      * (jms) This method is only invoked if there is a MuleMessage available to
198      * dispatch. The message dispatched from this method will be an
199      * <code>ExceptionMessage</code> which contains the exception thrown the
200      * MuleMessage and any context information.
201      * 
202      * @param message the MuleMessage being processed when the exception occurred
203      * @param target optional; the endpoint being dispatched or received on
204      *            when the error occurred. This is NOT the endpoint that the message
205      *            will be disptched on and is only supplied to this method for
206      *            logging purposes
207      * @param t the exception thrown. This will be sent with the ExceptionMessage
208      * @see ExceptionMessage
209      */
210     protected void routeException(MuleEvent event, MessageProcessor target, Throwable t)
211     {
212         if (!messageProcessors.isEmpty())
213         {
214             try
215             {
216                 logger.error("Message being processed is: " + (event.getMessage().getPayloadForLogging()));
217                 String component = "Unknown";
218                 if (event.getFlowConstruct() != null)
219                 {
220                     component = event.getFlowConstruct().getName();
221                 }
222                 EndpointURI endpointUri = event.getEndpoint().getEndpointURI();
223 
224                 // Create an ExceptionMessage which contains the original payload, the exception, and some additional context info.
225                 ExceptionMessage msg = new ExceptionMessage(event, t, component, endpointUri);
226                 MuleMessage exceptionMessage = new DefaultMuleMessage(msg, event.getMessage(), muleContext);
227 
228                 // Create an outbound router with all endpoints configured on the exception strategy
229                 MulticastingRouter router = new MulticastingRouter()
230                 {
231                     @Override
232                     protected void setMessageProperties(FlowConstruct session, MuleMessage message, MessageProcessor target)
233                     {
234                         // No reply-to or correlation for exception targets, at least for now anyway.
235                     }
236                 };
237                 router.setRoutes(getMessageProcessors());
238                 router.setMuleContext(muleContext);
239                 
240                 // Route the ExceptionMessage to the new router
241                 router.process(new DefaultMuleEvent(exceptionMessage, event));
242             }
243             catch (Exception e)
244             {
245                 logFatal(event, e);
246             }
247         }
248     }
249 
250     protected void closeStream(MuleMessage message)
251     {
252         if (muleContext == null || muleContext.isDisposing() || muleContext.isDisposed())
253         {
254             return;
255         }
256         if (message != null
257             && muleContext.getRegistry().lookupObject(MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE) != null)
258         {
259             ((StreamCloserService) muleContext.getRegistry().lookupObject(
260                     MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE)).closeStream(message.getPayload());
261         }
262     }
263 
264     /**
265      * Used to log the error passed into this Exception Listener
266      * 
267      * @param t the exception thrown
268      */
269     protected void logException(Throwable t)
270     {
271         MuleException muleException = ExceptionHelper.getRootMuleException(t);
272         if (muleException != null)
273         {
274             logger.error(muleException.getDetailedMessage());
275         }
276         else
277         {
278             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
279         }
280     }
281 
282     /**
283      * Logs a fatal error message to the logging system. This should be used mostly
284      * if an error occurs in the exception listener itself. This implementation logs
285      * the the message itself to the logs if it is not null
286      * 
287      * @param message The MuleMessage currently being processed
288      * @param t the fatal exception to log
289      */
290     protected void logFatal(MuleEvent event, Throwable t)
291     {
292         logger.fatal(
293             "Failed to dispatch message to error queue after it failed to process.  This may cause message loss."
294                             + (event.getMessage() == null ? "" : "Logging Message here: \n" + event.getMessage().toString()), t);
295     }
296 
297     public boolean isInitialised()
298     {
299         return initialised.get();
300     }
301 
302     /**
303      * Fires a server notification to all registered
304      * {@link org.mule.api.context.notification.ExceptionNotificationListener}
305      * eventManager.
306      * 
307      * @param notification the notification to fire.
308      */
309     protected void fireNotification(ExceptionNotification notification)
310     {
311         if (muleContext != null)
312         {
313             muleContext.fireNotification(notification);
314         }
315         else if (logger.isWarnEnabled())
316         {
317             logger.debug("MuleContext is not yet available for firing notifications, ignoring event: "
318                          + notification);
319         }
320     }
321 
322     public WildcardFilter getCommitTxFilter()
323     {
324         return commitTxFilter;
325     }
326 
327     public void setCommitTxFilter(WildcardFilter commitTxFilter)
328     {
329         this.commitTxFilter = commitTxFilter;
330     }
331 
332     public boolean isEnableNotifications()
333     {
334         return enableNotifications;
335     }
336 
337     public void setEnableNotifications(boolean enableNotifications)
338     {
339         this.enableNotifications = enableNotifications;
340     }
341 
342     public WildcardFilter getRollbackTxFilter()
343     {
344         return rollbackTxFilter;
345     }
346 
347     public void setRollbackTxFilter(WildcardFilter rollbackTxFilter)
348     {
349         this.rollbackTxFilter = rollbackTxFilter;
350     }
351     
352     @Override
353     protected List<MessageProcessor> getOwnedMessageProcessors()
354     {
355         return messageProcessors;
356     }
357 }