/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.handler.advice;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.message.AdviceMessage;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;
/**
* Used to advise {@link MessageHandler}s.
* Two expressions 'onSuccessExpression' and 'onFailureExpression' are evaluated when
* appropriate. If the evaluation returns a result, a message is sent to the onSuccessChannel
* or onFailureChannel as appropriate; the message is an {@link AdviceMessage}
* containing the evaluation result in its payload and the {@code inputMessage} property containing
* the original message that was sent to the endpoint.
* The failure expression is NOT evaluated if the success expression throws an exception.
*
* @author Gary Russell
* @author Artem Bilan
* @since 2.2
*
*/
public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHandlerAdvice {
private volatile Expression onSuccessExpression;
private volatile MessageChannel successChannel;
private volatile String successChannelName;
private volatile Expression onFailureExpression;
private volatile MessageChannel failureChannel;
private volatile String failureChannelName;
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
private volatile boolean trapException = false;
private volatile boolean returnFailureExpressionResult = false;
private volatile boolean propagateOnSuccessEvaluationFailures;
private volatile EvaluationContext evaluationContext;
/**
* Set the expression to evaluate against the message after a successful
* handler invocation.
* @param onSuccessExpression the SpEL expression.
* @since 4.3.7
*/
public void setOnSuccessExpressionString(String onSuccessExpression) {
this.onSuccessExpression = new SpelExpressionParser().parseExpression(onSuccessExpression);
}
/**
* Set the expression to evaluate against the message after a successful
* handler invocation.
* @param onSuccessExpression the SpEL expression.
* @since 5.0
*/
public void setOnSuccessExpression(Expression onSuccessExpression) {
this.onSuccessExpression = onSuccessExpression;
}
/**
* Set the expression to evaluate against the message after a successful
* handler invocation.
* @param onSuccessExpression the SpEL expression.
* @deprecated in favor of {@link #setOnSuccessExpression(Expression)}
*/
@Deprecated
public void setExpressionOnSuccess(Expression onSuccessExpression) {
this.onSuccessExpression = onSuccessExpression;
}
/**
* Set the expression to evaluate against the root message after a failed
* handler invocation. The exception is available as the variable {@code #exception}
* @param onFailureExpression the SpEL expression.
* @since 4.3.7
*/
public void setOnFailureExpressionString(String onFailureExpression) {
this.onFailureExpression = new SpelExpressionParser().parseExpression(onFailureExpression);
}
/**
* Set the expression to evaluate against the root message after a failed
* handler invocation. The exception is available as the variable {@code #exception}
* @param onFailureExpression the SpEL expression.
* @since 5.0
*/
public void setOnFailureExpression(Expression onFailureExpression) {
this.onFailureExpression = onFailureExpression;
}
/**
* Set the expression to evaluate against the root message after a failed
* handler invocation. The exception is available as the variable {@code #exception}
* @param onFailureExpression the SpEL expression.
* @deprecated in favor of {@link #setOnFailureExpression(Expression)}
*/
@Deprecated
public void setExpressionOnFailure(Expression onFailureExpression) {
this.onFailureExpression = onFailureExpression;
}
/**
* Set the channel to which to send the {@link AdviceMessage} after evaluating the
* success expression.
* @param successChannel the channel.
*/
public void setSuccessChannel(MessageChannel successChannel) {
this.successChannel = successChannel;
}
/**
* Set the channel name to which to send the {@link AdviceMessage} after evaluating
* the success expression.
* @param successChannelName the channel name.
* @since 4.3.7
*/
public void setSuccessChannelName(String successChannelName) {
this.successChannelName = successChannelName;
}
/**
* Set the channel to which to send the {@link ErrorMessage} after evaluating the
* failure expression.
* @param failureChannel the channel.
*/
public void setFailureChannel(MessageChannel failureChannel) {
this.failureChannel = failureChannel;
}
/**
* Set the channel name to which to send the {@link ErrorMessage} after evaluating the
* failure expression.
* @param failureChannelName the channel name.
* @since 4.3.7
*/
public void setFailureChannelName(String failureChannelName) {
this.failureChannelName = failureChannelName;
}
/**
* If true, any exception will be caught and null returned.
* Default false.
* @param trapException true to trap Exceptions.
*/
public void setTrapException(boolean trapException) {
this.trapException = trapException;
}
/**
* If true, the result of evaluating the onFailureExpression will
* be returned as the result of AbstractReplyProducingMessageHandler.handleRequestMessage(Message).
*
* @param returnFailureExpressionResult true to return the result of the evaluation.
*/
public void setReturnFailureExpressionResult(boolean returnFailureExpressionResult) {
this.returnFailureExpressionResult = returnFailureExpressionResult;
}
/**
* If true and an onSuccess expression evaluation fails with an exception, the
* exception will be thrown to the caller. If false, the exception is caught. Default
* false. Ignored for onFailure expression evaluation - the original exception will be
* propagated (unless trapException is true).
* @param propagateOnSuccessEvaluationFailures The
* propagateOnSuccessEvaluationFailures to set.
*/
public void setPropagateEvaluationFailures(boolean propagateOnSuccessEvaluationFailures) {
this.propagateOnSuccessEvaluationFailures = propagateOnSuccessEvaluationFailures;
}
@Override
protected void onInit() throws Exception {
super.onInit();
if (this.getBeanFactory() != null) {
this.messagingTemplate.setBeanFactory(this.getBeanFactory());
}
}
@Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
try {
Object result = callback.execute();
if (this.onSuccessExpression != null) {
this.evaluateSuccessExpression(message);
}
return result;
}
catch (Exception e) {
Exception actualException = this.unwrapExceptionIfNecessary(e);
if (this.onFailureExpression != null) {
Object evalResult = this.evaluateFailureExpression(message, actualException);
if (this.returnFailureExpressionResult) {
return evalResult;
}
}
if (!this.trapException) {
throw actualException;
}
return null;
}
}
private void evaluateSuccessExpression(Message<?> message) throws Exception {
Object evalResult;
boolean evaluationFailed = false;
try {
evalResult = this.onSuccessExpression.getValue(this.prepareEvaluationContextToUse(null), message);
}
catch (Exception e) {
evalResult = e;
evaluationFailed = true;
}
if (this.successChannel == null && this.successChannelName != null && getChannelResolver() != null) {
this.successChannel = getChannelResolver().resolveDestination(this.successChannelName);
}
if (evalResult != null && this.successChannel != null) {
AdviceMessage<?> resultMessage = new AdviceMessage<Object>(evalResult, message);
this.messagingTemplate.send(this.successChannel, resultMessage);
}
if (evaluationFailed && this.propagateOnSuccessEvaluationFailures) {
throw (Exception) evalResult;
}
}
private Object evaluateFailureExpression(Message<?> message, Exception exception) throws Exception {
Object evalResult;
try {
evalResult = this.onFailureExpression.getValue(this.prepareEvaluationContextToUse(exception), message);
}
catch (Exception e) {
evalResult = e;
logger.error("Failure expression evaluation failed for " + message + ": " + e.getMessage());
}
if (this.failureChannel == null && this.failureChannelName != null && getChannelResolver() != null) {
this.failureChannel = getChannelResolver().resolveDestination(this.failureChannelName);
}
if (evalResult != null && this.failureChannel != null) {
MessagingException messagingException = new MessageHandlingExpressionEvaluatingAdviceException(message,
"Handler Failed", this.unwrapThrowableIfNecessary(exception), evalResult);
ErrorMessage resultMessage = new ErrorMessage(messagingException);
this.messagingTemplate.send(this.failureChannel, resultMessage);
}
return evalResult;
}
protected StandardEvaluationContext createEvaluationContext() {
return ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
}
/**
* If we don't need variables (i.e., exception is null)
* we can use a singleton context; otherwise we need a new one each time.
* @param exception
* @return The context.
*/
private EvaluationContext prepareEvaluationContextToUse(Exception exception) {
EvaluationContext evaluationContextToUse;
if (exception != null) {
evaluationContextToUse = this.createEvaluationContext();
evaluationContextToUse.setVariable("exception", exception);
}
else {
if (this.evaluationContext == null) {
this.evaluationContext = this.createEvaluationContext();
}
evaluationContextToUse = this.evaluationContext;
}
return evaluationContextToUse;
}
public static class MessageHandlingExpressionEvaluatingAdviceException extends MessagingException {
private static final long serialVersionUID = 1L;
private final Object evaluationResult;
public MessageHandlingExpressionEvaluatingAdviceException(Message<?> message, String description,
Throwable cause, Object evaluationResult) {
super(message, description, cause);
this.evaluationResult = evaluationResult;
}
public Object getEvaluationResult() {
return this.evaluationResult;
}
}
}