Coverage Report - org.mule.AbstractExceptionListener
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractExceptionListener
32%
45/140
19%
13/68
2.367
 
 1  
 /*
 2  
  * $Id: AbstractExceptionListener.java 12370 2008-07-17 13:11:17Z tcarlson $
 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;
 12  
 
 13  
 import org.mule.api.MessagingException;
 14  
 import org.mule.api.MuleContext;
 15  
 import org.mule.api.MuleEvent;
 16  
 import org.mule.api.MuleEventContext;
 17  
 import org.mule.api.MuleException;
 18  
 import org.mule.api.MuleMessage;
 19  
 import org.mule.api.config.MuleProperties;
 20  
 import org.mule.api.context.MuleContextAware;
 21  
 import org.mule.api.endpoint.EndpointURI;
 22  
 import org.mule.api.endpoint.ImmutableEndpoint;
 23  
 import org.mule.api.endpoint.InvalidEndpointTypeException;
 24  
 import org.mule.api.endpoint.OutboundEndpoint;
 25  
 import org.mule.api.lifecycle.Disposable;
 26  
 import org.mule.api.lifecycle.Initialisable;
 27  
 import org.mule.api.lifecycle.InitialisationException;
 28  
 import org.mule.api.lifecycle.LifecycleException;
 29  
 import org.mule.api.routing.RoutingException;
 30  
 import org.mule.api.transaction.Transaction;
 31  
 import org.mule.api.transaction.TransactionException;
 32  
 import org.mule.api.util.StreamCloserService;
 33  
 import org.mule.config.ExceptionHelper;
 34  
 import org.mule.config.i18n.CoreMessages;
 35  
 import org.mule.context.notification.ExceptionNotification;
 36  
 import org.mule.message.ExceptionMessage;
 37  
 import org.mule.routing.filters.WildcardFilter;
 38  
 import org.mule.transaction.TransactionCoordination;
 39  
 import org.mule.transport.NullPayload;
 40  
 import org.mule.util.CollectionUtils;
 41  
 
 42  
 import java.beans.ExceptionListener;
 43  
 import java.util.Iterator;
 44  
 import java.util.List;
 45  
 
 46  
 import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
 47  
 import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
 48  
 
 49  
 import org.apache.commons.logging.Log;
 50  
 import org.apache.commons.logging.LogFactory;
 51  
 
 52  
 /**
 53  
  * <code>AbstractExceptionListener</code> is a base implementation that custom
 54  
  * Exception Listeners can override. It provides template methods for handling the
 55  
  * for base types of exceptions plus allows multimple endpoints to be associated with
 56  
  * this exception listener and provides an implementation for dispatching exception
 57  
  * events from this Listener.
 58  
  */
 59  2030
 public abstract class AbstractExceptionListener implements ExceptionListener, Initialisable, Disposable, MuleContextAware
 60  
 {
 61  
     /**
 62  
      * logger used by this class
 63  
      */
 64  2030
     protected transient Log logger = LogFactory.getLog(getClass());
 65  
 
 66  2030
     protected List endpoints = new CopyOnWriteArrayList();
 67  
 
 68  2030
     protected AtomicBoolean initialised = new AtomicBoolean(false);
 69  
 
 70  
     protected MuleContext muleContext;
 71  
 
 72  
     protected WildcardFilter rollbackTxFilter;
 73  
     protected WildcardFilter commitTxFilter;
 74  
 
 75  2030
     protected boolean enableNotifications = true;
 76  
 
 77  
     public void setMuleContext(MuleContext context)
 78  
     {
 79  2012
         this.muleContext = context;
 80  2012
     }
 81  
 
 82  
     public List getEndpoints()
 83  
     {
 84  0
         return endpoints;
 85  
     }
 86  
 
 87  
     public void setEndpoints(List endpoints)
 88  
     {
 89  0
         if (endpoints != null)
 90  
         {
 91  0
             this.endpoints.clear();
 92  
             // Ensure all endpoints are outbound endpoints
 93  
             // This will go when we start dropping suport for 1.4 and start using 1.5
 94  0
             for (Iterator it = endpoints.iterator(); it.hasNext();)
 95  
             {
 96  0
                 ImmutableEndpoint endpoint = (ImmutableEndpoint) it.next();
 97  0
                 if (!(endpoint instanceof OutboundEndpoint))
 98  
                 {
 99  0
                     throw new InvalidEndpointTypeException(CoreMessages.exceptionListenerMustUseOutboundEndpoint(this,
 100  
                         endpoint));
 101  
                 }
 102  0
             }
 103  0
             this.endpoints.addAll(endpoints);
 104  
         }
 105  
         else
 106  
         {
 107  0
             throw new IllegalArgumentException("List of endpoints = null");
 108  
         }
 109  0
     }
 110  
 
 111  
     public void addEndpoint(OutboundEndpoint endpoint)
 112  
     {
 113  0
         if (endpoint != null)
 114  
         {
 115  0
             endpoints.add(endpoint);
 116  
         }
 117  0
     }
 118  
 
 119  
     public boolean removeEndpoint(OutboundEndpoint endpoint)
 120  
     {
 121  0
         return endpoints.remove(endpoint);
 122  
     }
 123  
 
 124  
     public void exceptionThrown(Exception e)
 125  
     {
 126  4
         if (enableNotifications)
 127  
         {
 128  4
             fireNotification(new ExceptionNotification(e));
 129  
         }
 130  
 
 131  4
         logException(e);
 132  4
         handleTransaction(e);
 133  
 
 134  4
         Throwable t = getExceptionType(e, RoutingException.class);
 135  4
         if (t != null)
 136  
         {
 137  0
             RoutingException re = (RoutingException) t;
 138  0
             handleRoutingException(re.getUmoMessage(), re.getEndpoint(), e);
 139  0
             return;
 140  
         }
 141  
 
 142  4
         t = getExceptionType(e, MessagingException.class);
 143  4
         if (t != null)
 144  
         {
 145  0
             MessagingException me = (MessagingException) t;
 146  0
             handleMessagingException(me.getUmoMessage(), e);
 147  0
             return;
 148  
         }
 149  
 
 150  4
         t = getExceptionType(e, LifecycleException.class);
 151  4
         if (t != null)
 152  
         {
 153  0
             LifecycleException le = (LifecycleException) t;
 154  0
             handleLifecycleException(le.getComponent(), e);
 155  0
             if (RequestContext.getEventContext() != null)
 156  
             {
 157  0
                 handleMessagingException(RequestContext.getEventContext().getMessage(), e);
 158  
             }
 159  
             else
 160  
             {
 161  0
                 logger.info("There is no current event available, routing Null message with the exception");
 162  0
                 handleMessagingException(new DefaultMuleMessage(NullPayload.getInstance()), e);
 163  
             }
 164  0
             return;
 165  
         }
 166  
 
 167  4
         handleStandardException(e);
 168  4
     }
 169  
 
 170  
     protected Throwable getExceptionType(Throwable t, Class exceptionType)
 171  
     {
 172  24
         while (t != null)
 173  
         {
 174  12
             if (exceptionType.isAssignableFrom(t.getClass()))
 175  
             {
 176  0
                 return t;
 177  
             }
 178  
 
 179  12
             t = t.getCause();
 180  
         }
 181  
 
 182  12
         return null;
 183  
     }
 184  
 
 185  
     /**
 186  
      * The initialise method is call every time the Exception stategy is assigned to
 187  
      * a service or connector. This implementation ensures that initialise is
 188  
      * called only once. The actual initialisation code is contained in the
 189  
      * <code>doInitialise()</code> method.
 190  
      *
 191  
      * @throws InitialisationException
 192  
      */
 193  
     public final synchronized void initialise() throws InitialisationException
 194  
     {
 195  472
         if (!initialised.get())
 196  
         {
 197  472
             doInitialise(muleContext);
 198  472
             initialised.set(true);
 199  
         }
 200  472
     }
 201  
 
 202  
     protected void doInitialise(MuleContext muleContext) throws InitialisationException
 203  
     {
 204  472
         logger.info("Initialising exception listener: " + toString());
 205  472
     }
 206  
 
 207  
     /**
 208  
      * If there is a current transaction this method will mark it for rollback This
 209  
      * method should not be called if an event is routed from this exception handler
 210  
      * to an endpoint that should take part in the current transaction
 211  
      */
 212  
     protected void handleTransaction(Throwable t)
 213  
     {
 214  12
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 215  
 
 216  12
         if (tx == null)
 217  
         {
 218  12
             return;
 219  
         }
 220  
         //Work with the root exception, not anything thaat wraps it
 221  0
         t = ExceptionHelper.getRootException(t);
 222  
 
 223  0
         if (rollbackTxFilter == null && commitTxFilter == null)
 224  
         {
 225  
             //By default, rollback the transaction
 226  0
             rollbackTransaction();
 227  
         }
 228  0
         else if (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
 229  
         {
 230  
             //the rollback filter take preceedence over th ecommit filter
 231  0
             rollbackTransaction();
 232  
         }
 233  0
         else if (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()))
 234  
         {
 235  
             //we only have to rollback if the commitTxFilter does NOT match
 236  0
             rollbackTransaction();
 237  
         }
 238  0
     }
 239  
 
 240  
     protected void rollbackTransaction()
 241  
     {
 242  0
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 243  
         try
 244  
         {
 245  0
             if (tx != null)
 246  
             {
 247  0
                 tx.setRollbackOnly();
 248  
             }
 249  
         }
 250  0
         catch (TransactionException e)
 251  
         {
 252  0
             logException(e);
 253  0
         }
 254  0
     }
 255  
 
 256  
     /**
 257  
      * Routes the current exception to an error endpoint such as a Dead Letter Queue
 258  
      * (jms) This method is only invoked if there is a MuleMessage available to
 259  
      * dispatch. The message dispatched from this method will be an
 260  
      * <code>ExceptionMessage</code> which contains the exception thrown the
 261  
      * MuleMessage and any context information.
 262  
      *
 263  
      * @param message        the MuleMessage being processed when the exception occurred
 264  
      * @param failedEndpoint optional; the endpoint being dispatched or received on
 265  
      *                       when the error occurred. This is NOT the endpoint that the message
 266  
      *                       will be disptched on and is only supplied to this method for
 267  
      *                       logging purposes
 268  
      * @param t              the exception thrown. This will be sent with the ExceptionMessage
 269  
      * @see ExceptionMessage
 270  
      */
 271  
     protected void routeException(MuleMessage message, ImmutableEndpoint failedEndpoint, Throwable t)
 272  
     {
 273  4
         List endpoints = getEndpoints(t);
 274  4
         if (CollectionUtils.isNotEmpty(endpoints))
 275  
         {
 276  
             try
 277  
             {
 278  0
                 logger.error("Message being processed is: " + (message == null ? "null" : message.toString()));
 279  0
                 MuleEventContext ctx = RequestContext.getEventContext();
 280  0
                 String component = "Unknown";
 281  0
                 EndpointURI endpointUri = null;
 282  0
                 if (ctx != null)
 283  
                 {
 284  0
                     if (ctx.getService() != null)
 285  
                     {
 286  0
                         component = ctx.getService().getName();
 287  
                     }
 288  0
                     endpointUri = ctx.getEndpointURI();
 289  
                 }
 290  0
                 else if (failedEndpoint != null)
 291  
                 {
 292  0
                     endpointUri = failedEndpoint.getEndpointURI();
 293  
                 }
 294  
                 ExceptionMessage msg;
 295  0
                 msg = new ExceptionMessage(getErrorMessagePayload(message), t, component, endpointUri);
 296  
 
 297  
                 MuleMessage exceptionMessage;
 298  0
                 if (ctx == null)
 299  
                 {
 300  0
                     exceptionMessage = new DefaultMuleMessage(msg);
 301  
                 }
 302  
                 else
 303  
                 {
 304  0
                     exceptionMessage = new DefaultMuleMessage(msg, ctx.getMessage());
 305  
                 }
 306  
 
 307  0
                 for (int i = 0; i < endpoints.size(); i++)
 308  
                 {
 309  0
                     OutboundEndpoint endpoint = (OutboundEndpoint) endpoints.get(i);
 310  0
                     MuleEvent exceptionEvent = new DefaultMuleEvent(exceptionMessage, endpoint, new DefaultMuleSession(
 311  
                         exceptionMessage, new MuleSessionHandler(), muleContext), true);
 312  0
                     exceptionEvent = RequestContext.setEvent(exceptionEvent);
 313  0
                     endpoint.send(exceptionEvent);
 314  
 
 315  0
                     if (logger.isDebugEnabled())
 316  
                     {
 317  0
                         logger.debug("routed Exception message via " + endpoint);
 318  
                     }
 319  
                 }
 320  
             }
 321  0
             catch (MuleException e)
 322  
             {
 323  0
                 logFatal(message, e);
 324  0
                 closeStream(message);
 325  0
             }
 326  
         }
 327  
         else
 328  
         {
 329  4
             handleTransaction(t);
 330  4
             closeStream(message);
 331  
         }
 332  4
     }
 333  
 
 334  
     protected void closeStream(MuleMessage message)
 335  
     {
 336  4
         if (muleContext != null)
 337  
         {
 338  4
             ((StreamCloserService) muleContext.getRegistry().lookupObject(
 339  
                 MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE)).closeStream(message.getPayload());
 340  
         }
 341  4
     }
 342  
 
 343  
     protected Object getErrorMessagePayload(MuleMessage message)
 344  
     {
 345  
         try
 346  
         {
 347  0
             return message.getPayloadAsString();
 348  
         }
 349  0
         catch (Exception e)
 350  
         {
 351  0
             logException(e);
 352  0
             logger.info("Failed to read message payload as string, using raw payload");
 353  0
             return message.getPayload();
 354  
         }
 355  
     }
 356  
 
 357  
     /**
 358  
      * Returns an endpoint for the given exception. ExceptionListeners can have
 359  
      * multiple endpoints registered on them. This methods allows custom
 360  
      * implementations to control which endpoint is used based on the exception
 361  
      * thrown. This implementation simply returns the first endpoint in the list.
 362  
      *
 363  
      * @param t the exception thrown
 364  
      * @return The endpoint used to dispatch an exception message on or null if there
 365  
      *         are no endpoints registered
 366  
      */
 367  
     protected List getEndpoints(Throwable t)
 368  
     {
 369  4
         if (!endpoints.isEmpty())
 370  
         {
 371  0
             return endpoints;
 372  
         }
 373  
         else
 374  
         {
 375  4
             return null;
 376  
         }
 377  
     }
 378  
 
 379  
     /**
 380  
      * Used to log the error passed into this Exception Listener
 381  
      *
 382  
      * @param t the exception thrown
 383  
      */
 384  
     protected void logException(Throwable t)
 385  
     {
 386  0
         MuleException umoe = ExceptionHelper.getRootMuleException(t);
 387  0
         if (umoe != null)
 388  
         {
 389  0
             logger.error(umoe.getDetailedMessage());
 390  
         }
 391  
         else
 392  
         {
 393  0
             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
 394  
         }
 395  0
     }
 396  
 
 397  
     /**
 398  
      * Logs a fatal error message to the logging system. This should be used mostly
 399  
      * if an error occurs in the exception listener itself. This implementation logs
 400  
      * the the message itself to the logs if it is not null
 401  
      *
 402  
      * @param message The MuleMessage currently being processed
 403  
      * @param t       the fatal exception to log
 404  
      */
 405  
     protected void logFatal(MuleMessage message, Throwable t)
 406  
     {
 407  0
         logger.fatal(
 408  
                 "Failed to dispatch message to error queue after it failed to process.  This may cause message loss."
 409  
                         + (message == null ? "" : "Logging Message here: \n" + message.toString()), t);
 410  0
     }
 411  
 
 412  
     public boolean isInitialised()
 413  
     {
 414  0
         return initialised.get();
 415  
     }
 416  
 
 417  
 
 418  
     public void dispose()
 419  
     {
 420  
         // Template method
 421  0
     }
 422  
 
 423  
     /**
 424  
      * Fires a server notification to all registered
 425  
      * {@link org.mule.api.context.notification.listener.ExceptionNotificationListener}
 426  
      * eventManager.
 427  
      *
 428  
      * @param notification the notification to fire.
 429  
      */
 430  
     protected void fireNotification(ExceptionNotification notification)
 431  
     {
 432  4
         if (muleContext != null)
 433  
         {
 434  4
             muleContext.fireNotification(notification);
 435  
         }
 436  0
         else if (logger.isWarnEnabled())
 437  
         {
 438  0
             logger.debug("MuleContext is not yet available for firing notifications, ignoring event: " + notification);
 439  
         }
 440  4
     }
 441  
 
 442  
     public WildcardFilter getCommitTxFilter()
 443  
     {
 444  0
         return commitTxFilter;
 445  
     }
 446  
 
 447  
     public void setCommitTxFilter(WildcardFilter commitTxFilter)
 448  
     {
 449  0
         this.commitTxFilter = commitTxFilter;
 450  0
     }
 451  
 
 452  
     public boolean isEnableNotifications()
 453  
     {
 454  0
         return enableNotifications;
 455  
     }
 456  
 
 457  
     public void setEnableNotifications(boolean enableNotifications)
 458  
     {
 459  0
         this.enableNotifications = enableNotifications;
 460  0
     }
 461  
 
 462  
     public WildcardFilter getRollbackTxFilter()
 463  
     {
 464  0
         return rollbackTxFilter;
 465  
     }
 466  
 
 467  
     public void setRollbackTxFilter(WildcardFilter rollbackTxFilter)
 468  
     {
 469  0
         this.rollbackTxFilter = rollbackTxFilter;
 470  0
     }
 471  
 
 472  
     /**
 473  
      * A messaging exception is thrown when an excpetion occurs during normal message
 474  
      * processing. A <code>MessagingException</code> holds a reference to the
 475  
      * current message that is passed into this method
 476  
      *
 477  
      * @param message the current message being processed
 478  
      * @param e       the top level exception thrown. This may be a Messaging exception or
 479  
      *                some wrapper exception
 480  
      * @see MessagingException
 481  
      */
 482  
     public abstract void handleMessagingException(MuleMessage message, Throwable e);
 483  
 
 484  
     /**
 485  
      * A routing exception is thrown when an excpetion occurs during normal message
 486  
      * processing A <code>RoutingException</code> holds a reference to the current
 487  
      * message and te endpoint being routing to or from when the error occurred. Both
 488  
      * are passed into this method
 489  
      *
 490  
      * @param message  the current message being processed
 491  
      * @param endpoint the endpoint being dispatched to or received from when the
 492  
      *                 error occurred
 493  
      * @param e        the top level exception thrown. This may be a Messaging exception or
 494  
      *                 some wrapper exception
 495  
      * @see RoutingException
 496  
      */
 497  
     public abstract void handleRoutingException(MuleMessage message, ImmutableEndpoint endpoint, Throwable e);
 498  
 
 499  
     /**
 500  
      * DefaultLifecyclePhase exceptions are thrown when an error occurs during an object's
 501  
      * lifecycle call such as start, stop or initialise. The exception contains a
 502  
      * reference to the object that failed which can be used for more informative
 503  
      * logging.
 504  
      *
 505  
      * @param service the object that failed during a lifecycle call
 506  
      * @param e       the top level exception thrown. This may or may not be the
 507  
      *                <code>LifecycleException</code> but a lifecycle exception will be
 508  
      *                present in the exception stack.
 509  
      * @see LifecycleException
 510  
      */
 511  
     public abstract void handleLifecycleException(Object component, Throwable e);
 512  
 
 513  
     /**
 514  
      * A handler for all other exceptions
 515  
      *
 516  
      * @param e the top level exception thrown
 517  
      */
 518  
     public abstract void handleStandardException(Throwable e);
 519  
 
 520  
 
 521  
 }