/*
* 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.lang.String.format;
import static org.apache.commons.lang.StringUtils.abbreviate;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.i18n.I18nMessage;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.metadata.DataType;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.processor.Processor;
import org.mule.runtime.core.config.ExceptionHelper;
import org.mule.runtime.core.routing.filters.RegExFilter;
import org.mule.runtime.core.routing.filters.WildcardFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;
/**
* <code>MessagingException</code> is a general message exception thrown when errors specific to Message processing occur..
*/
public class MessagingException extends MuleException {
public static final String PAYLOAD_INFO_KEY = "Payload";
public static final String PAYLOAD_TYPE_INFO_KEY = "Payload Type";
/**
* Serial version
*/
private static final long serialVersionUID = 6941498759267936649L;
/**
* The Message being processed when the error occurred
*/
protected transient Message muleMessage;
/**
* The MuleEvent being processed when the error occurred
*/
protected final transient Event event;
protected transient Event processedEvent;
private boolean causeRollback;
private boolean handled;
private transient Processor failingMessageProcessor;
/**
* @deprecated use MessagingException(Message, MuleEvent)
*/
@Deprecated
public MessagingException(I18nMessage message, Message muleMessage, MuleContext context) {
super(message);
this.muleMessage = muleMessage;
this.event = null;
setMessage(generateMessage(message, context));
}
public MessagingException(I18nMessage message, Event event) {
super(message);
this.event = event;
extractMuleMessage(event);
setMessage(generateMessage(message, null));
}
public MessagingException(I18nMessage message, Event event, Processor failingMessageProcessor) {
super(message);
this.event = event;
extractMuleMessage(event);
this.failingMessageProcessor = failingMessageProcessor;
setMessage(generateMessage(message, null));
}
/**
* @deprecated use MessagingException(Message, MuleEvent, Throwable)
*/
@Deprecated
public MessagingException(I18nMessage message, Message muleMessage, MuleContext context, Throwable cause) {
super(message, cause);
this.muleMessage = muleMessage;
this.event = null;
setMessage(generateMessage(message, context));
}
public MessagingException(I18nMessage message, Event event, Throwable cause) {
super(message, cause);
this.event = event;
extractMuleMessage(event);
setMessage(generateMessage(message, null));
}
public MessagingException(I18nMessage message, Event event, Throwable cause, Processor failingMessageProcessor) {
super(message, cause);
this.event = event;
extractMuleMessage(event);
this.failingMessageProcessor = failingMessageProcessor;
setMessage(generateMessage(message, null));
}
public MessagingException(Event event, Throwable cause) {
super(cause);
this.event = event;
extractMuleMessage(event);
setMessage(generateMessage(getI18nMessage(), null));
}
public MessagingException(Event event, MessagingException original) {
super(original.getI18nMessage(), original.getCause());
this.event = event;
this.failingMessageProcessor = original.getFailingMessageProcessor();
this.causeRollback = original.causedRollback();
this.handled = original.handled();
original.getInfo().forEach((key, value) -> addInfo((String) key, value));
extractMuleMessage(event);
setMessage(original.getMessage());
}
public MessagingException(Event event, Throwable cause, Processor failingMessageProcessor) {
super(cause);
this.event = event;
extractMuleMessage(event);
this.failingMessageProcessor = failingMessageProcessor;
setMessage(generateMessage(getI18nMessage(), null));
}
protected String generateMessage(I18nMessage message, MuleContext muleContext) {
StringBuilder buf = new StringBuilder(80);
if (message != null) {
buf.append(message.getMessage()).append(".");
}
if (muleMessage != null) {
if (MuleException.isVerboseExceptions()) {
Object payload = muleMessage.getPayload().getValue();
if (muleMessage.getPayload().getDataType().isStreamType()) {
addInfo(PAYLOAD_INFO_KEY, abbreviate(payload.toString(), 1000));
} else {
if (payload != null) {
addInfo(PAYLOAD_TYPE_INFO_KEY, muleMessage.getPayload().getDataType().getType().getName());
if (muleContext != null) {
// TODO MULE-10266 review how the transformationService is obtained when building an exception.
try {
addInfo(PAYLOAD_INFO_KEY,
muleContext.getTransformationService().transform(muleMessage, DataType.STRING).getPayload().getValue());
} catch (Exception e) {
addInfo(PAYLOAD_INFO_KEY, format("%s while getting payload: %s", e.getClass().getName(), e.getMessage()));
}
addInfo(PAYLOAD_INFO_KEY, muleMessage.toString());
}
} else {
addInfo(PAYLOAD_TYPE_INFO_KEY, Objects.toString(null));
addInfo(PAYLOAD_INFO_KEY, Objects.toString(null));
}
}
}
} else {
buf.append("The current Message is null!");
addInfo(PAYLOAD_INFO_KEY, Objects.toString(null));
}
return buf.toString();
}
/**
* @deprecated use {@link #getEvent().getMessage()} instead
*/
@Deprecated
public Message getMuleMessage() {
if ((getEvent() != null)) {
return getEvent().getMessage();
}
return muleMessage;
}
/**
* @return event associated with the exception
*/
public Event getEvent() {
return processedEvent != null ? processedEvent : event;
}
/**
* Sets the event that should be processed once this exception is caught
*
* @param processedEvent event bounded to the exception
*/
public void setProcessedEvent(Event processedEvent) {
if (processedEvent != null) {
this.processedEvent = processedEvent;
extractMuleMessage(processedEvent);
} else {
this.processedEvent = null;
this.muleMessage = null;
}
}
/**
* Evaluates if the exception was caused (instance of) by the provided exception type
*
* @param e exception type to check against
* @return true if the cause exception is an instance of the provided exception type
*/
public boolean causedBy(final Class e) {
if (e == null) {
throw new IllegalArgumentException("Class cannot be null");
}
return (ExceptionHelper.traverseCauseHierarchy(this, causeException -> {
if (e.isAssignableFrom(causeException.getClass())) {
return causeException;
}
return null;
}) != null);
}
/**
* Evaluates if the exception was caused by the type and only the type provided exception type i,e: if cause exception is
* NullPointerException will only return true if provided exception type is NullPointerException
*
* @param e exception type to check against
* @return true if the cause exception is exaclty the provided exception type
*/
public boolean causedExactlyBy(final Class e) {
if (e == null) {
throw new IllegalArgumentException("Class cannot be null");
}
return (ExceptionHelper.traverseCauseHierarchy(this, causeException -> {
if (causeException.getClass().equals(e)) {
return causeException;
}
return null;
}) != null);
}
/**
* @return the exception thrown by the failing message processor
*/
public Throwable getRootCause() {
Throwable rootException = ExceptionHelper.getRootException(this);
if (rootException == null) {
rootException = this;
}
return rootException;
}
/**
* Checks the cause exception type name matches the provided regex. Supports any java regex plus *, * prefix, * sufix
*
* @param regex regular expression to match against the exception type name
* @return true if the exception matches the regex, false otherwise
*/
public boolean causeMatches(final String regex) {
if (regex == null) {
throw new IllegalArgumentException("regex cannot be null");
}
return (ExceptionHelper.traverseCauseHierarchy(this, e -> {
WildcardFilter wildcardFilter = new WildcardFilter(regex);
if (wildcardFilter.accept(e.getClass().getName())) {
return e;
}
try {
RegExFilter regExFilter = new RegExFilter(regex);
if (regExFilter.accept(e.getClass().getName())) {
return e;
}
} catch (Exception regexEx) {
// Do nothing, regex such as *, *something, something* will fail,
// just don't match
}
return null;
})) != null;
}
/**
* Signals if the exception cause rollback of any current transaction if any or if the message source should rollback incoming
* message
*
* @return true if exception cause rollback, false otherwise
*/
public boolean causedRollback() {
return causeRollback;
}
/**
* Marks exception as rollback cause. Useful for message sources that can provide some rollback mechanism.
*
* @param causeRollback
*/
public void setCauseRollback(boolean causeRollback) {
this.causeRollback = causeRollback;
}
/**
* Marks an exception as handled so it won't be re-throwed
*
* @param handled true if the exception must be mark as handled, false otherwise
*/
public void setHandled(boolean handled) {
this.handled = handled;
}
/**
* Signals if exception has been handled or not
*
* @return true if exception has been handled, false otherwise
*/
public boolean handled() {
return handled;
}
/**
* @return MessageProcessor that causes the failure
*/
public Processor getFailingMessageProcessor() {
return failingMessageProcessor;
}
protected void extractMuleMessage(Event event) {
this.muleMessage = event == null ? null : event.getMessage();
}
private void writeObject(ObjectOutputStream out) throws Exception {
out.defaultWriteObject();
if (this.failingMessageProcessor instanceof Serializable) {
out.writeBoolean(true);
out.writeObject(this.failingMessageProcessor);
} else {
out.writeBoolean(false);
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
boolean failingMessageProcessorWasSerialized = in.readBoolean();
if (failingMessageProcessorWasSerialized) {
this.failingMessageProcessor = (Processor) in.readObject();
}
}
}