Coverage Report - org.mule.exception.AbstractExceptionListener
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractExceptionListener
0%
0/126
0%
0/74
0
AbstractExceptionListener$1
0%
0/2
N/A
0
 
 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  0
 public abstract class AbstractExceptionListener implements Initialisable, Disposable, MuleContextAware
 65  
 {
 66  
     /**
 67  
      * logger used by this class
 68  
      */
 69  0
     protected transient Log logger = LogFactory.getLog(getClass());
 70  
 
 71  0
     @SuppressWarnings("unchecked")
 72  
     protected List<MessageProcessor> messageProcessors = new CopyOnWriteArrayList();
 73  
 
 74  0
     protected AtomicBoolean initialised = new AtomicBoolean(false);
 75  
 
 76  
     protected MuleContext muleContext;
 77  
 
 78  
     protected WildcardFilter rollbackTxFilter;
 79  
     protected WildcardFilter commitTxFilter;
 80  
 
 81  0
     protected boolean enableNotifications = true;
 82  
 
 83  
     public void setMuleContext(MuleContext context)
 84  
     {
 85  0
         this.muleContext = context;
 86  0
     }
 87  
 
 88  
     public List<MessageProcessor> getMessageProcessors()
 89  
     {
 90  0
         return messageProcessors;
 91  
     }
 92  
 
 93  
     public void setMessageProcessors(List<OutboundEndpoint> processors)
 94  
     {
 95  0
         if (processors != null)
 96  
         {
 97  0
             this.messageProcessors.clear();
 98  0
             this.messageProcessors.addAll(processors);
 99  
         }
 100  
         else
 101  
         {
 102  0
             throw new IllegalArgumentException("List of targets = null");
 103  
         }
 104  0
     }
 105  
 
 106  
     public void addEndpoint(MessageProcessor processor)
 107  
     {
 108  0
         if (processor != null)
 109  
         {
 110  0
             messageProcessors.add(processor);
 111  
         }
 112  0
     }
 113  
 
 114  
     public boolean removeMessageProcessor(MessageProcessor processor)
 115  
     {
 116  0
         return messageProcessors.remove(processor);
 117  
     }
 118  
 
 119  
     protected Throwable getExceptionType(Throwable t, Class exceptionType)
 120  
     {
 121  0
         while (t != null)
 122  
         {
 123  0
             if (exceptionType.isAssignableFrom(t.getClass()))
 124  
             {
 125  0
                 return t;
 126  
             }
 127  
 
 128  0
             t = t.getCause();
 129  
         }
 130  
 
 131  0
         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  0
         if (!initialised.get())
 145  
         {
 146  0
             doInitialise(muleContext);
 147  0
             initialised.set(true);
 148  
         }
 149  0
     }
 150  
 
 151  
     protected void doInitialise(MuleContext muleContext) throws InitialisationException
 152  
     {
 153  0
         logger.info("Initialising exception listener: " + toString());
 154  0
     }
 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  0
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 164  
 
 165  0
         if (tx == null)
 166  
         {
 167  0
             return;
 168  
         }
 169  
         // Work with the root exception, not anything thaat wraps it
 170  0
         t = ExceptionHelper.getRootException(t);
 171  
 
 172  0
         if (rollbackTxFilter == null && commitTxFilter == null)
 173  
         {
 174  
             // By default, rollback the transaction
 175  0
             rollbackTransaction();
 176  
         }
 177  0
         else if (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
 178  
         {
 179  
             // the rollback filter take preceedence over th ecommit filter
 180  0
             rollbackTransaction();
 181  
         }
 182  0
         else if (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()))
 183  
         {
 184  
             // we only have to rollback if the commitTxFilter does NOT match
 185  0
             rollbackTransaction();
 186  
         }
 187  0
     }
 188  
 
 189  
     protected void rollbackTransaction()
 190  
     {
 191  0
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 192  
         try
 193  
         {
 194  0
             if (tx != null)
 195  
             {
 196  0
                 tx.setRollbackOnly();
 197  
             }
 198  
         }
 199  0
         catch (TransactionException e)
 200  
         {
 201  0
             logException(e);
 202  0
         }
 203  0
     }
 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  0
         List endpoints = getMessageProcessors(t);
 223  0
         if (CollectionUtils.isNotEmpty(endpoints))
 224  
         {
 225  
             try
 226  
             {
 227  0
                 logger.error("Message being processed is: " + (message == null ? "null" : message.toString()));
 228  0
                 MuleEventContext ctx = RequestContext.getEventContext();
 229  0
                 String component = "Unknown";
 230  0
                 EndpointURI endpointUri = null;
 231  0
                 if (ctx != null)
 232  
                 {
 233  0
                     if (ctx.getFlowConstruct() != null)
 234  
                     {
 235  0
                         component = ctx.getFlowConstruct().getName();
 236  
                     }
 237  0
                     endpointUri = ctx.getEndpointURI();
 238  
                 }
 239  0
                 else if (target instanceof ImmutableEndpoint)
 240  
                 {
 241  0
                     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  0
                 if (message.getPayload() instanceof Serializable)
 248  
                 {
 249  0
                     payload = (Serializable) message.getPayload();
 250  
                 }
 251  
                 else
 252  
                 {
 253  0
                     payload = message.getPayloadAsString();
 254  
                 }
 255  0
                 ExceptionMessage msg = new ExceptionMessage(payload, t, component, endpointUri);
 256  
 
 257  
                 MuleMessage exceptionMessage;
 258  0
                 if (ctx == null)
 259  
                 {
 260  0
                     exceptionMessage = new DefaultMuleMessage(msg, muleContext);
 261  
                 }
 262  
                 else
 263  
                 {
 264  0
                     exceptionMessage = new DefaultMuleMessage(msg, ctx.getMessage(), muleContext);
 265  
                 }
 266  
 
 267  0
                 if (ctx != null && ctx.getFlowConstruct() != null && ctx.getFlowConstruct() instanceof Service)
 268  
                 {
 269  0
                     OutboundRouter router = createOutboundRouter();
 270  0
                     router.process(new DefaultMuleEvent(exceptionMessage, RequestContext.getEvent()));
 271  0
                 }
 272  
                 else
 273  
                 {
 274  
                     // As the service is not available an outbound router cannot be
 275  
                     // used to route the exception message.
 276  0
                     customRouteExceptionMessage(exceptionMessage);
 277  
                 }
 278  
             }
 279  0
             catch (Exception e)
 280  
             {
 281  0
                 logFatal(message, e);
 282  0
                 closeStream(message);
 283  0
             }
 284  
         }
 285  
         else
 286  
         {
 287  0
             handleTransaction(t);
 288  0
             closeStream(message);
 289  
         }
 290  0
     }
 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  0
         for (int i = 0; i < messageProcessors.size(); i++)
 299  
         {
 300  0
             MessageProcessor processor = messageProcessors.get(i);
 301  0
             if (((DefaultMuleMessage) exceptionMessage).isConsumable())
 302  
             {
 303  0
                 throw new MessagingException(
 304  
                     CoreMessages.cannotCopyStreamPayload(exceptionMessage.getPayload().getClass().getName()),
 305  
                     exceptionMessage);
 306  
             }
 307  
 
 308  0
             MuleMessage clonedMessage = new DefaultMuleMessage(exceptionMessage.getPayload(),
 309  
                 exceptionMessage, muleContext);
 310  0
             MuleEvent exceptionEvent = null;
 311  0
             if (processor instanceof OutboundEndpoint)
 312  
             {
 313  0
                 exceptionEvent = new DefaultMuleEvent(clonedMessage, (OutboundEndpoint) processor,
 314  
                     new DefaultMuleSession(muleContext));
 315  
             }
 316  
             else
 317  
             {
 318  0
                 exceptionEvent = new DefaultMuleEvent(clonedMessage, RequestContext.getEvent().getEndpoint(),
 319  
                     new DefaultMuleSession(muleContext));
 320  
             }
 321  0
             exceptionEvent = RequestContext.setEvent(exceptionEvent);
 322  
 
 323  0
             processor.process(exceptionEvent);
 324  
 
 325  0
             if (logger.isDebugEnabled())
 326  
             {
 327  0
                 logger.debug("routed Exception message via " + processor);
 328  
             }
 329  
         }
 330  0
     }
 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  0
         MulticastingRouter router = new MulticastingRouter()
 340  0
         {
 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  0
             }
 349  
         };
 350  0
         router.setRoutes(new ArrayList<MessageProcessor>(getMessageProcessors()));
 351  0
         router.setMuleContext(muleContext);
 352  0
         return router;
 353  
     }
 354  
 
 355  
     protected void closeStream(MuleMessage message)
 356  
     {
 357  0
         if (muleContext == null || muleContext.isDisposing() || muleContext.isDisposed())
 358  
         {
 359  0
             return;
 360  
         }
 361  0
         if (message != null
 362  
             && muleContext.getRegistry().lookupObject(MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE) != null)
 363  
         {
 364  0
             ((StreamCloserService) muleContext.getRegistry().lookupObject(
 365  
                     MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE)).closeStream(message.getPayload());
 366  
         }
 367  0
     }
 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  0
         if (!messageProcessors.isEmpty())
 382  
         {
 383  0
             return messageProcessors;
 384  
         }
 385  
         else
 386  
         {
 387  0
             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  0
         MuleException muleException = ExceptionHelper.getRootMuleException(t);
 399  0
         if (muleException != null)
 400  
         {
 401  0
             logger.error(muleException.getDetailedMessage());
 402  
         }
 403  
         else
 404  
         {
 405  0
             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
 406  
         }
 407  0
     }
 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  0
         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  0
     }
 423  
 
 424  
     public boolean isInitialised()
 425  
     {
 426  0
         return initialised.get();
 427  
     }
 428  
 
 429  
     public void dispose()
 430  
     {
 431  
         // Template method
 432  0
     }
 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  0
         if (muleContext != null)
 444  
         {
 445  0
             muleContext.fireNotification(notification);
 446  
         }
 447  0
         else if (logger.isWarnEnabled())
 448  
         {
 449  0
             logger.debug("MuleContext is not yet available for firing notifications, ignoring event: "
 450  
                          + notification);
 451  
         }
 452  0
     }
 453  
 
 454  
     public WildcardFilter getCommitTxFilter()
 455  
     {
 456  0
         return commitTxFilter;
 457  
     }
 458  
 
 459  
     public void setCommitTxFilter(WildcardFilter commitTxFilter)
 460  
     {
 461  0
         this.commitTxFilter = commitTxFilter;
 462  0
     }
 463  
 
 464  
     public boolean isEnableNotifications()
 465  
     {
 466  0
         return enableNotifications;
 467  
     }
 468  
 
 469  
     public void setEnableNotifications(boolean enableNotifications)
 470  
     {
 471  0
         this.enableNotifications = enableNotifications;
 472  0
     }
 473  
 
 474  
     public WildcardFilter getRollbackTxFilter()
 475  
     {
 476  0
         return rollbackTxFilter;
 477  
     }
 478  
 
 479  
     public void setRollbackTxFilter(WildcardFilter rollbackTxFilter)
 480  
     {
 481  0
         this.rollbackTxFilter = rollbackTxFilter;
 482  0
     }
 483  
 }