View Javadoc

1   /*
2    * $Id: AbstractExceptionStrategy.java 23054 2011-10-02 05:31:18Z dirk.olmes $
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.api.MuleContext;
16  import org.mule.api.MuleEvent;
17  import org.mule.api.MuleException;
18  import org.mule.api.MuleMessage;
19  import org.mule.api.config.MuleProperties;
20  import org.mule.api.construct.FlowConstruct;
21  import org.mule.api.context.notification.ServerNotification;
22  import org.mule.api.exception.RollbackSourceCallback;
23  import org.mule.api.lifecycle.InitialisationException;
24  import org.mule.api.processor.MessageProcessor;
25  import org.mule.api.security.SecurityException;
26  import org.mule.api.transaction.Transaction;
27  import org.mule.api.transaction.TransactionException;
28  import org.mule.api.util.StreamCloserService;
29  import org.mule.config.ExceptionHelper;
30  import org.mule.context.notification.ExceptionNotification;
31  import org.mule.context.notification.SecurityNotification;
32  import org.mule.management.stats.FlowConstructStatistics;
33  import org.mule.management.stats.ServiceStatistics;
34  import org.mule.message.ExceptionMessage;
35  import org.mule.processor.AbstractMessageProcessorOwner;
36  import org.mule.routing.filters.WildcardFilter;
37  import org.mule.routing.outbound.MulticastingRouter;
38  import org.mule.transaction.TransactionCoordination;
39  import org.mule.util.CollectionUtils;
40  
41  import java.net.URI;
42  import java.util.List;
43  import java.util.concurrent.CopyOnWriteArrayList;
44  import java.util.concurrent.atomic.AtomicBoolean;
45  
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  
49  /**
50   * This is the base class for exception strategies which contains several helper methods.  However, you should 
51   * probably inherit from <code>AbstractMessagingExceptionStrategy</code> (if you are creating a Messaging Exception Strategy) 
52   * or <code>AbstractSystemExceptionStrategy</code> (if you are creating a System Exception Strategy) rather than directly from this class.
53   */
54  public abstract class AbstractExceptionStrategy extends AbstractMessageProcessorOwner
55  {
56      protected transient Log logger = LogFactory.getLog(getClass());
57  
58      @SuppressWarnings("unchecked")
59      protected List<MessageProcessor> messageProcessors = new CopyOnWriteArrayList();
60  
61      protected AtomicBoolean initialised = new AtomicBoolean(false);
62  
63      protected WildcardFilter rollbackTxFilter;
64      protected WildcardFilter commitTxFilter;
65  
66      protected boolean enableNotifications = true;
67  
68      public AbstractExceptionStrategy(MuleContext muleContext)
69      {
70          super();
71          setMuleContext(muleContext);
72      }
73      
74      protected boolean isRollback(Throwable t)
75      {
76          if (rollbackTxFilter == null && commitTxFilter == null)
77          {
78              return true;
79          }
80          else
81          {
82              return (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
83                     || (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()));
84          }
85      }
86      
87      public List<MessageProcessor> getMessageProcessors()
88      {
89          return messageProcessors;
90      }
91  
92      public void setMessageProcessors(List<MessageProcessor> processors)
93      {
94          if (processors != null)
95          {
96              this.messageProcessors.clear();
97              this.messageProcessors.addAll(processors);
98          }
99          else
100         {
101             throw new IllegalArgumentException("List of targets = null");
102         }
103     }
104 
105     public void addEndpoint(MessageProcessor processor)
106     {
107         if (processor != null)
108         {
109             messageProcessors.add(processor);
110         }
111     }
112 
113     public boolean removeMessageProcessor(MessageProcessor processor)
114     {
115         return messageProcessors.remove(processor);
116     }
117 
118     protected Throwable getExceptionType(Throwable t, Class exceptionType)
119     {
120         while (t != null)
121         {
122             if (exceptionType.isAssignableFrom(t.getClass()))
123             {
124                 return t;
125             }
126 
127             t = t.getCause();
128         }
129 
130         return null;
131     }
132 
133     /**
134      * The initialise method is call every time the Exception stategy is assigned to
135      * a service or connector. This implementation ensures that initialise is called
136      * only once. The actual initialisation code is contained in the
137      * <code>doInitialise()</code> method.
138      * 
139      * @throws InitialisationException
140      */
141     public final synchronized void initialise() throws InitialisationException
142     {
143         super.initialise();
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     protected void fireNotification(Exception ex)
157     {
158         if (enableNotifications)
159         {
160             if (ex instanceof SecurityException)
161             {
162                 fireNotification(new SecurityNotification((SecurityException) ex, SecurityNotification.SECURITY_AUTHENTICATION_FAILED));
163             }
164             else
165             {
166                 fireNotification(new ExceptionNotification(ex));
167             }
168         }
169     }
170 
171     /**
172      * Routes the current exception to an error endpoint such as a Dead Letter Queue
173      * (jms) This method is only invoked if there is a MuleMessage available to
174      * dispatch. The message dispatched from this method will be an
175      * <code>ExceptionMessage</code> which contains the exception thrown the
176      * MuleMessage and any context information.
177      * 
178      * @param event the MuleEvent being processed when the exception occurred
179      * @param t the exception thrown. This will be sent with the ExceptionMessage
180      * @see ExceptionMessage
181      */
182     protected void routeException(MuleEvent event, Throwable t)
183     {
184         if (!messageProcessors.isEmpty())
185         {
186             try
187             {
188                 logger.error("Message being processed is: " + (event.getMessage().getPayloadForLogging()));
189                 String component = "Unknown";
190                 if (event.getFlowConstruct() != null)
191                 {
192                     component = event.getFlowConstruct().getName();
193                 }
194                 URI endpointUri = event.getMessageSourceURI();
195 
196                 // Create an ExceptionMessage which contains the original payload, the exception, and some additional context info.
197                 ExceptionMessage msg = new ExceptionMessage(event, t, component, endpointUri);
198                 MuleMessage exceptionMessage = new DefaultMuleMessage(msg, event.getMessage(), muleContext);
199 
200                 // Create an outbound router with all endpoints configured on the exception strategy
201                 MulticastingRouter router = new MulticastingRouter()
202                 {
203                     @Override
204                     protected void setMessageProperties(FlowConstruct session, MuleMessage message, MessageProcessor target)
205                     {
206                         // No reply-to or correlation for exception targets, at least for now anyway.
207                     }
208                 };
209                 router.setRoutes(getMessageProcessors());
210                 router.setMuleContext(muleContext);
211                 
212                 // Route the ExceptionMessage to the new router
213                 router.process(new DefaultMuleEvent(exceptionMessage, event));
214             }
215             catch (Exception e)
216             {
217                 logFatal(event, e);
218             }
219         }
220         
221         List<MessageProcessor> processors = getMessageProcessors();
222         FlowConstructStatistics statistics = event.getFlowConstruct().getStatistics();
223         if (CollectionUtils.isNotEmpty(processors) && statistics instanceof ServiceStatistics)
224         {
225             if (statistics.isEnabled())
226             {
227                 for (MessageProcessor endpoint : processors)
228                 {
229                     ((ServiceStatistics) statistics).getOutboundRouterStat().incrementRoutedMessage(endpoint);
230                 }
231             }
232         }
233     }
234 
235     protected void commit()
236     {
237         Transaction tx = TransactionCoordination.getInstance().getTransaction();
238         if (tx != null)
239         {
240             try
241             {
242                 tx.commit();
243             }
244             catch (TransactionException e)
245             {
246                 logger.error(e);
247             }
248         }
249     }
250 
251     protected void rollback(RollbackSourceCallback rollbackMethod)
252     {
253         Transaction tx = TransactionCoordination.getInstance().getTransaction();
254         if (tx != null)
255         {
256             try
257             {
258                 tx.rollback();
259                 
260                 // TODO The following was in the catch clause of TransactionTemplate previously.  
261                 // Do we need to do this here?  If so, where can we store these variables (suspendedXATx, joinedExternal)
262                 // so that they are available to us in the exception handler?
263                 //
264                 //if (suspendedXATx != null)
265                 //{
266                 //  resumeXATransaction(suspendedXATx);
267                 //}
268                 //if (joinedExternal != null)
269                 //{
270                 //    TransactionCoordination.getInstance().unbindTransaction(joinedExternal);
271                 //}
272             }
273             catch (TransactionException e)
274             {
275                 logger.error(e);
276             }
277         }
278         else if (rollbackMethod != null)
279         {
280             rollbackMethod.rollback();
281         }
282     }
283 
284     protected void closeStream(MuleMessage message)
285     {
286         if (muleContext == null || muleContext.isDisposing() || muleContext.isDisposed())
287         {
288             return;
289         }
290         if (message != null
291             && muleContext.getRegistry().lookupObject(MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE) != null)
292         {
293             ((StreamCloserService) muleContext.getRegistry().lookupObject(
294                     MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE)).closeStream(message.getPayload());
295         }
296     }
297 
298     /**
299      * Used to log the error passed into this Exception Listener
300      * 
301      * @param t the exception thrown
302      */
303     protected void logException(Throwable t)
304     {
305         MuleException muleException = ExceptionHelper.getRootMuleException(t);
306         if (muleException != null)
307         {
308             logger.error(muleException.getDetailedMessage());
309         }
310         else
311         {
312             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
313         }
314     }
315 
316     /**
317      * Logs a fatal error message to the logging system. This should be used mostly
318      * if an error occurs in the exception listener itself. This implementation logs
319      * the the message itself to the logs if it is not null
320      * 
321      * @param event The MuleEvent currently being processed
322      * @param t the fatal exception to log
323      */
324     protected void logFatal(MuleEvent event, Throwable t)
325     {
326         FlowConstructStatistics statistics = event.getFlowConstruct().getStatistics();
327         if (statistics != null && statistics.isEnabled())
328         {
329             statistics.incFatalError();
330         }
331 
332         logger.fatal(
333             "Failed to dispatch message to error queue after it failed to process.  This may cause message loss."
334                             + (event.getMessage() == null ? "" : "Logging Message here: \n" + event.getMessage().toString()), t);
335     }
336 
337     public boolean isInitialised()
338     {
339         return initialised.get();
340     }
341 
342     /**
343      * Fires a server notification to all registered
344      * {@link org.mule.api.context.notification.ExceptionNotificationListener}
345      * eventManager.
346      * 
347      * @param notification the notification to fire.
348      */
349     protected void fireNotification(ServerNotification notification)
350     {
351         if (muleContext != null)
352         {
353             muleContext.fireNotification(notification);
354         }
355         else if (logger.isWarnEnabled())
356         {
357             logger.debug("MuleContext is not yet available for firing notifications, ignoring event: "
358                          + notification);
359         }
360     }
361 
362     public WildcardFilter getCommitTxFilter()
363     {
364         return commitTxFilter;
365     }
366 
367     public void setCommitTxFilter(WildcardFilter commitTxFilter)
368     {
369         this.commitTxFilter = commitTxFilter;
370     }
371 
372     public boolean isEnableNotifications()
373     {
374         return enableNotifications;
375     }
376 
377     public void setEnableNotifications(boolean enableNotifications)
378     {
379         this.enableNotifications = enableNotifications;
380     }
381 
382     public WildcardFilter getRollbackTxFilter()
383     {
384         return rollbackTxFilter;
385     }
386 
387     public void setRollbackTxFilter(WildcardFilter rollbackTxFilter)
388     {
389         this.rollbackTxFilter = rollbackTxFilter;
390     }
391     
392     @Override
393     protected List<MessageProcessor> getOwnedMessageProcessors()
394     {
395         return messageProcessors;
396     }
397 }