/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core.exception;
import static java.text.MessageFormat.format;
import static org.apache.commons.lang.StringUtils.defaultString;
import static org.mule.runtime.core.api.context.notification.EnrichedNotificationInfo.createInfo;
import static org.mule.runtime.core.context.notification.SecurityNotification.SECURITY_AUTHENTICATION_FAILED;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.security.SecurityException;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.GlobalNameableObject;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.context.notification.ServerNotification;
import org.mule.runtime.core.api.exception.MessagingExceptionHandler;
import org.mule.runtime.core.api.processor.Processor;
import org.mule.runtime.core.config.ExceptionHelper;
import org.mule.runtime.core.context.notification.ExceptionNotification;
import org.mule.runtime.core.context.notification.SecurityNotification;
import org.mule.runtime.core.management.stats.FlowConstructStatistics;
import org.mule.runtime.core.message.ExceptionMessage;
import org.mule.runtime.core.processor.AbstractMessageProcessorOwner;
import org.mule.runtime.core.routing.filters.WildcardFilter;
import org.mule.runtime.core.routing.outbound.MulticastingRouter;
import org.mule.runtime.core.transaction.TransactionCoordination;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the base class for exception strategies which contains several helper methods. However, you should probably inherit
* from <code>AbstractMessagingExceptionStrategy</code> (if you are creating a Messaging Exception Strategy) or
* <code>AbstractSystemExceptionStrategy</code> (if you are creating a System Exception Strategy) rather than directly from this
* class.
*/
public abstract class AbstractExceptionListener extends AbstractMessageProcessorOwner implements GlobalNameableObject {
protected static final String NOT_SET = "<not set>";
protected transient Logger logger = LoggerFactory.getLogger(getClass());
protected List<Processor> messageProcessors = new CopyOnWriteArrayList<>();
protected AtomicBoolean initialised = new AtomicBoolean(false);
protected WildcardFilter rollbackTxFilter;
protected WildcardFilter commitTxFilter;
protected boolean enableNotifications = true;
protected String logException = "mel:true";
protected String globalName;
@Override
public String getGlobalName() {
return globalName;
}
@Override
public void setGlobalName(String globalName) {
this.globalName = globalName;
}
public AbstractExceptionListener() {
super.setMessagingExceptionHandler(new MessagingExceptionHandlerToSystemAdapter(muleContext));
}
protected boolean isRollback(Throwable t) {
// Work with the root exception, not anything thaat wraps it
t = ExceptionHelper.getRootException(t);
if (rollbackTxFilter == null && commitTxFilter == null) {
return true;
} else {
return (rollbackTxFilter != null && rollbackTxFilter.accept(t.getClass().getName()))
|| (commitTxFilter != null && !commitTxFilter.accept(t.getClass().getName()));
}
}
public List<Processor> getMessageProcessors() {
return messageProcessors;
}
public void setMessageProcessors(List<Processor> processors) {
if (processors != null) {
this.messageProcessors.clear();
this.messageProcessors.addAll(processors);
} else {
throw new IllegalArgumentException("List of targets = null");
}
}
public void addEndpoint(Processor processor) {
if (processor != null) {
messageProcessors.add(processor);
}
}
public boolean removeMessageProcessor(Processor processor) {
return messageProcessors.remove(processor);
}
protected Throwable getExceptionType(Throwable t, Class<? extends Throwable> exceptionType) {
while (t != null) {
if (exceptionType.isAssignableFrom(t.getClass())) {
return t;
}
t = t.getCause();
}
return null;
}
/**
* The initialise method is call every time the Exception stategy is assigned to a service or connector. This implementation
* ensures that initialise is called only once. The actual initialisation code is contained in the <code>doInitialise()</code>
* method.
*
* @throws InitialisationException
*/
@Override
public final synchronized void initialise() throws InitialisationException {
if (!initialised.get()) {
super.initialise();
doInitialise(muleContext);
initialised.set(true);
}
}
protected void doInitialise(MuleContext context) throws InitialisationException {
logger.info("Initialising exception listener: " + toString());
}
protected void fireNotification(Exception ex, Event event) {
if (enableNotifications) {
if (ex.getCause() != null && getCause(ex) instanceof SecurityException) {
fireNotification(new SecurityNotification((SecurityException) getCause(ex), SECURITY_AUTHENTICATION_FAILED));
} else {
fireNotification(new ExceptionNotification(createInfo(event, ex, null), flowConstruct));
}
}
}
private Throwable getCause(Exception ex) {
return ex.getCause() instanceof TypedException ? ex.getCause().getCause() : ex.getCause();
}
/**
* Routes the current exception to an error endpoint such as a Dead Letter Queue (jms) This method is only invoked if there is a
* Message available to dispatch. The message dispatched from this method will be an <code>ExceptionMessage</code> which
* contains the exception thrown the Message and any context information.
*
* @param event the MuleEvent being processed when the exception occurred
* @param flowConstruct the flow that was processing the event when the exception occurred.
* @param t the exception thrown. This will be sent with the ExceptionMessage
* @see ExceptionMessage
*/
protected Event routeException(Event event, FlowConstruct flowConstruct, Throwable t) {
Event result = event;
if (!messageProcessors.isEmpty()) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Message being processed is: "
+ (muleContext.getTransformationService().getPayloadForLogging(event.getMessage())));
}
// Create an ExceptionMessage which contains the original payload, the exception, and some additional context info.
ExceptionMessage msg =
new ExceptionMessage(event, t, flowConstruct.getName(), event.getContext().getOriginatingConnectorName());
Message exceptionMessage = Message.builder(event.getMessage()).payload(msg).build();
MulticastingRouter router = buildRouter();
router.setRoutes(getMessageProcessors());
router.setMuleContext(muleContext);
// Route the ExceptionMessage to the new router
result = router.route(Event.builder(event).message(exceptionMessage).build());
} catch (Exception e) {
logFatal(event, e);
}
}
return result;
}
protected MulticastingRouter buildRouter() {
// Create an outbound router with all endpoints configured on the exception strategy
MulticastingRouter router = new MulticastingRouter();
return router;
}
protected void closeStream(Message message) {
if (muleContext == null || muleContext.isDisposing() || muleContext.isDisposed()) {
return;
}
if (message != null) {
muleContext.getStreamCloserService().closeStream(message.getPayload().getValue());
}
}
/**
* Used to log the error passed into this Exception Listener
*
* @param t the exception thrown
*/
protected void logException(Throwable t, Event event) {
if (this.muleContext.getExpressionManager().evaluateBoolean(logException, event, flowConstruct, true, true)) {
doLogException(t);
}
}
protected void doLogException(Throwable t) {
MuleException muleException = ExceptionHelper.getRootMuleException(t);
if (muleException != null) {
logger.error(muleException.getDetailedMessage());
} else {
logger.error("Caught exception in Exception Strategy: " + t.getMessage(), t);
}
}
/**
* Logs a fatal error message to the logging system. This should be used mostly if an error occurs in the exception listener
* itself. This implementation logs the the message itself to the logs if it is not null
*
* @param event The MuleEvent currently being processed
* @param t the fatal exception to log
*/
protected void logFatal(Event event, Throwable t) {
FlowConstructStatistics statistics = flowConstruct.getStatistics();
if (statistics != null && statistics.isEnabled()) {
statistics.incFatalError();
}
String logUniqueId = defaultString(event.getCorrelationId(), NOT_SET);
String printableLogMessage =
format("Message identification summary here: id={0}, correlation={1}", logUniqueId, event.getGroupCorrelation());
logger.error("Failed to dispatch message to error queue after it failed to process. This may cause message loss. "
+ (event.getMessage() == null ? "" : printableLogMessage), t);
}
public boolean isInitialised() {
return initialised.get();
}
/**
* Fires a server notification to all registered
* {@link org.mule.runtime.core.api.context.notification.ExceptionNotificationListener} eventManager.
*
* @param notification the notification to fire.
*/
protected void fireNotification(ServerNotification notification) {
if (muleContext != null) {
muleContext.fireNotification(notification);
} else if (logger.isWarnEnabled()) {
logger.debug("MuleContext is not yet available for firing notifications, ignoring event: " + notification);
}
}
public WildcardFilter getCommitTxFilter() {
return commitTxFilter;
}
public void setCommitTxFilter(WildcardFilter commitTxFilter) {
this.commitTxFilter = commitTxFilter;
}
public boolean isEnableNotifications() {
return enableNotifications;
}
public void setEnableNotifications(boolean enableNotifications) {
this.enableNotifications = enableNotifications;
}
/**
* Determines whether the handled exception will be logged to its standard logger in the ERROR level before being handled.
*/
public String isLogException() {
return logException;
}
public void setLogException(String logException) {
this.logException = logException;
}
public WildcardFilter getRollbackTxFilter() {
return rollbackTxFilter;
}
public void setRollbackTxFilter(WildcardFilter rollbackTxFilter) {
this.rollbackTxFilter = rollbackTxFilter;
}
@Override
protected List<Processor> getOwnedMessageProcessors() {
return messageProcessors;
}
@Override
public void setMessagingExceptionHandler(MessagingExceptionHandler messagingExceptionHandler) {
return;
}
protected void commit() {
TransactionCoordination.getInstance().commitCurrentTransaction();
}
protected void rollback(Exception ex) {
if (TransactionCoordination.getInstance().getTransaction() != null) {
TransactionCoordination.getInstance().rollbackCurrentTransaction();
}
if (ex instanceof MessagingException) {
MessagingException messagingException = (MessagingException) ex;
messagingException.setCauseRollback(true);
}
}
}