Coverage Report - org.mule.exception.AbstractExceptionListener
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractExceptionListener
0%
0/123
0%
0/74
0
AbstractExceptionListener$1
0%
0/2
N/A
0
 
 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  0
 public abstract class AbstractExceptionListener extends AbstractMessageProcessorOwner
 62  
 {
 63  
     /**
 64  
      * logger used by this class
 65  
      */
 66  0
     protected transient Log logger = LogFactory.getLog(getClass());
 67  
 
 68  0
     @SuppressWarnings("unchecked")
 69  
     protected List<MessageProcessor> messageProcessors = new CopyOnWriteArrayList();
 70  
 
 71  0
     protected AtomicBoolean initialised = new AtomicBoolean(false);
 72  
 
 73  
     protected WildcardFilter rollbackTxFilter;
 74  
     protected WildcardFilter commitTxFilter;
 75  
 
 76  0
     protected boolean enableNotifications = true;
 77  
 
 78  
     public List<MessageProcessor> getMessageProcessors()
 79  
     {
 80  0
         return messageProcessors;
 81  
     }
 82  
 
 83  
     public void setMessageProcessors(List<MessageProcessor> processors)
 84  
     {
 85  0
         if (processors != null)
 86  
         {
 87  0
             this.messageProcessors.clear();
 88  0
             this.messageProcessors.addAll(processors);
 89  
         }
 90  
         else
 91  
         {
 92  0
             throw new IllegalArgumentException("List of targets = null");
 93  
         }
 94  0
     }
 95  
 
 96  
     public void addEndpoint(MessageProcessor processor)
 97  
     {
 98  0
         if (processor != null)
 99  
         {
 100  0
             messageProcessors.add(processor);
 101  
         }
 102  0
     }
 103  
 
 104  
     public boolean removeMessageProcessor(MessageProcessor processor)
 105  
     {
 106  0
         return messageProcessors.remove(processor);
 107  
     }
 108  
 
 109  
     protected Throwable getExceptionType(Throwable t, Class exceptionType)
 110  
     {
 111  0
         while (t != null)
 112  
         {
 113  0
             if (exceptionType.isAssignableFrom(t.getClass()))
 114  
             {
 115  0
                 return t;
 116  
             }
 117  
 
 118  0
             t = t.getCause();
 119  
         }
 120  
 
 121  0
         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  0
         super.initialise();
 135  0
         if (!initialised.get())
 136  
         {
 137  0
             doInitialise(muleContext);
 138  0
             initialised.set(true);
 139  
         }
 140  0
     }
 141  
 
 142  
     protected void doInitialise(MuleContext muleContext) throws InitialisationException
 143  
     {
 144  0
         logger.info("Initialising exception listener: " + toString());
 145  0
     }
 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  0
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 155  
 
 156  0
         if (tx == null)
 157  
         {
 158  0
             return;
 159  
         }
 160  
         // Work with the root exception, not anything thaat wraps it
 161  0
         t = ExceptionHelper.getRootException(t);
 162  
 
 163  0
         if (rollbackTxFilter == null && commitTxFilter == null)
 164  
         {
 165  
             // By default, rollback the transaction
 166  0
             rollbackTransaction();
 167  
         }
 168  0
         else if (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
 169  
         {
 170  
             // the rollback filter take preceedence over th ecommit filter
 171  0
             rollbackTransaction();
 172  
         }
 173  0
         else if (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()))
 174  
         {
 175  
             // we only have to rollback if the commitTxFilter does NOT match
 176  0
             rollbackTransaction();
 177  
         }
 178  0
     }
 179  
 
 180  
     protected void rollbackTransaction()
 181  
     {
 182  0
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 183  
         try
 184  
         {
 185  0
             if (tx != null)
 186  
             {
 187  0
                 tx.setRollbackOnly();
 188  
             }
 189  
         }
 190  0
         catch (TransactionException e)
 191  
         {
 192  0
             logException(e);
 193  0
         }
 194  0
     }
 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  0
         List endpoints = getMessageProcessors(t);
 214  0
         if (CollectionUtils.isNotEmpty(endpoints))
 215  
         {
 216  
             try
 217  
             {
 218  0
                 logger.error("Message being processed is: " + (message == null ? "null" : message.toString()));
 219  0
                 MuleEventContext ctx = RequestContext.getEventContext();
 220  0
                 String component = "Unknown";
 221  0
                 EndpointURI endpointUri = null;
 222  0
                 if (ctx != null)
 223  
                 {
 224  0
                     if (ctx.getFlowConstruct() != null)
 225  
                     {
 226  0
                         component = ctx.getFlowConstruct().getName();
 227  
                     }
 228  0
                     endpointUri = ctx.getEndpointURI();
 229  
                 }
 230  0
                 else if (target instanceof ImmutableEndpoint)
 231  
                 {
 232  0
                     endpointUri = ((ImmutableEndpoint)target).getEndpointURI();
 233  
                 }
 234  
                 
 235  0
                 ExceptionMessage msg = new ExceptionMessage(message.getPayload(), t, component, endpointUri);
 236  
                 MuleMessage exceptionMessage;
 237  0
                 if (ctx == null)
 238  
                 {
 239  0
                     exceptionMessage = new DefaultMuleMessage(msg, muleContext);
 240  
                 }
 241  
                 else
 242  
                 {
 243  0
                     exceptionMessage = new DefaultMuleMessage(msg, ctx.getMessage(), muleContext);
 244  
                 }
 245  
 
 246  0
                 if (ctx != null && ctx.getFlowConstruct() != null && ctx.getFlowConstruct() instanceof Service)
 247  
                 {
 248  0
                     OutboundRouter router = createOutboundRouter();
 249  0
                     router.process(new DefaultMuleEvent(exceptionMessage, RequestContext.getEvent()));
 250  0
                 }
 251  
                 else
 252  
                 {
 253  
                     // As the service is not available an outbound router cannot be
 254  
                     // used to route the exception message.
 255  0
                     customRouteExceptionMessage(exceptionMessage);
 256  
                 }
 257  
             }
 258  0
             catch (Exception e)
 259  
             {
 260  0
                 logFatal(message, e);
 261  0
                 closeStream(message);
 262  0
             }
 263  
         }
 264  
         else
 265  
         {
 266  0
             handleTransaction(t);
 267  0
             closeStream(message);
 268  
         }
 269  0
     }
 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  0
         int numProcessors = messageProcessors.size();
 278  0
         for (int i = 0; i < numProcessors; i++)
 279  
         {
 280  0
             MessageProcessor processor = messageProcessors.get(i);
 281  0
             if (numProcessors > 1 && ((DefaultMuleMessage) exceptionMessage).isConsumable())
 282  
             {
 283  0
                 throw new MessagingException(
 284  
                     CoreMessages.cannotCopyStreamPayload(exceptionMessage.getPayload().getClass().getName()),
 285  
                     exceptionMessage);
 286  
             }
 287  
 
 288  0
             MuleMessage clonedMessage = new DefaultMuleMessage(exceptionMessage.getPayload(),
 289  
                 exceptionMessage, muleContext);
 290  0
             MuleEvent exceptionEvent = null;
 291  0
             if (processor instanceof OutboundEndpoint)
 292  
             {
 293  0
                 exceptionEvent = new DefaultMuleEvent(clonedMessage, (OutboundEndpoint) processor,
 294  
                     new DefaultMuleSession(muleContext));
 295  
             }
 296  
             else
 297  
             {
 298  0
                 exceptionEvent = new DefaultMuleEvent(clonedMessage, RequestContext.getEvent().getEndpoint(),
 299  
                     new DefaultMuleSession(muleContext));
 300  
             }
 301  0
             exceptionEvent = RequestContext.setEvent(exceptionEvent);
 302  
 
 303  0
             processor.process(exceptionEvent);
 304  
 
 305  0
             if (logger.isDebugEnabled())
 306  
             {
 307  0
                 logger.debug("routed Exception message via " + processor);
 308  
             }
 309  
         }
 310  0
     }
 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  0
         MulticastingRouter router = new MulticastingRouter()
 320  0
         {
 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  0
             }
 329  
         };
 330  0
         router.setRoutes(new ArrayList<MessageProcessor>(getMessageProcessors()));
 331  0
         router.setMuleContext(muleContext);
 332  0
         return router;
 333  
     }
 334  
 
 335  
     protected void closeStream(MuleMessage message)
 336  
     {
 337  0
         if (muleContext == null || muleContext.isDisposing() || muleContext.isDisposed())
 338  
         {
 339  0
             return;
 340  
         }
 341  0
         if (message != null
 342  
             && muleContext.getRegistry().lookupObject(MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE) != null)
 343  
         {
 344  0
             ((StreamCloserService) muleContext.getRegistry().lookupObject(
 345  
                     MuleProperties.OBJECT_MULE_STREAM_CLOSER_SERVICE)).closeStream(message.getPayload());
 346  
         }
 347  0
     }
 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  0
         if (!messageProcessors.isEmpty())
 362  
         {
 363  0
             return messageProcessors;
 364  
         }
 365  
         else
 366  
         {
 367  0
             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  0
         MuleException muleException = ExceptionHelper.getRootMuleException(t);
 379  0
         if (muleException != null)
 380  
         {
 381  0
             logger.error(muleException.getDetailedMessage());
 382  
         }
 383  
         else
 384  
         {
 385  0
             logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
 386  
         }
 387  0
     }
 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  0
         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  0
     }
 403  
 
 404  
     public boolean isInitialised()
 405  
     {
 406  0
         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  0
         if (muleContext != null)
 419  
         {
 420  0
             muleContext.fireNotification(notification);
 421  
         }
 422  0
         else if (logger.isWarnEnabled())
 423  
         {
 424  0
             logger.debug("MuleContext is not yet available for firing notifications, ignoring event: "
 425  
                          + notification);
 426  
         }
 427  0
     }
 428  
 
 429  
     public WildcardFilter getCommitTxFilter()
 430  
     {
 431  0
         return commitTxFilter;
 432  
     }
 433  
 
 434  
     public void setCommitTxFilter(WildcardFilter commitTxFilter)
 435  
     {
 436  0
         this.commitTxFilter = commitTxFilter;
 437  0
     }
 438  
 
 439  
     public boolean isEnableNotifications()
 440  
     {
 441  0
         return enableNotifications;
 442  
     }
 443  
 
 444  
     public void setEnableNotifications(boolean enableNotifications)
 445  
     {
 446  0
         this.enableNotifications = enableNotifications;
 447  0
     }
 448  
 
 449  
     public WildcardFilter getRollbackTxFilter()
 450  
     {
 451  0
         return rollbackTxFilter;
 452  
     }
 453  
 
 454  
     public void setRollbackTxFilter(WildcardFilter rollbackTxFilter)
 455  
     {
 456  0
         this.rollbackTxFilter = rollbackTxFilter;
 457  0
     }
 458  
     
 459  
     @Override
 460  
     protected List<MessageProcessor> getOwnedMessageProcessors()
 461  
     {
 462  0
         return messageProcessors;
 463  
     }
 464  
 }