org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice.java

Source

/*
 * Copyright 2002-2019 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
 *
 *      https://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.beans.factory.BeanFactory;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.message.AdviceMessage;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.util.StringUtils;

/**
 * Used to advise {@link org.springframework.messaging.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.
 * <p>
 * When expressions are not configured, but channels are, the default expression is evaluated
 * just into a {@code payload} from the message.
 *
 * @author Gary Russell
 * @author Artem Bilan
 *
 * @since 2.2
 *
 */
public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHandlerAdvice {

    private static final Expression DEFAULT_EXPRESSION = new FunctionExpression<Message<?>>(Message::getPayload);

    private final MessagingTemplate messagingTemplate = new MessagingTemplate();

    private Expression onSuccessExpression;

    private MessageChannel successChannel;

    private String successChannelName;

    private Expression onFailureExpression;

    private MessageChannel failureChannel;

    private String failureChannelName;

    private boolean trapException = false;

    private boolean returnFailureExpressionResult = false;

    private boolean propagateOnSuccessEvaluationFailures;

    private EvaluationContext evaluationContext;

    /**
     * Set the expression to evaluate against the message after a successful
     * handler invocation.
     * Defaults to {@code payload}, if {@code successChannel} is configured.
     * @param onSuccessExpression the SpEL expression.
     * @since 4.3.7
     */
    public void setOnSuccessExpressionString(String onSuccessExpression) {
        setOnSuccessExpression(EXPRESSION_PARSER.parseExpression(onSuccessExpression));
    }

    /**
     * Set the expression to evaluate against the message after a successful
     * handler invocation.
     * Defaults to {@code payload}, if {@code successChannel} is configured.
     * @param onSuccessExpression the SpEL expression.
     * @since 5.0
     */
    public void setOnSuccessExpression(@Nullable 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) {
        setOnSuccessExpression(onSuccessExpression);
    }

    /**
     * Set the expression to evaluate against the root message after a failed
     * handler invocation. The exception is available as the variable {@code #exception}.
     * Defaults to {@code payload}, if {@code failureChannel} is configured.
     * @param onFailureExpression the SpEL expression.
     * @since 4.3.7
     */
    public void setOnFailureExpressionString(String onFailureExpression) {
        setOnFailureExpression(EXPRESSION_PARSER.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}.
     * Defaults to {@code payload}, if {@code failureChannel} is configured.
     * @param onFailureExpression the SpEL expression.
     * @since 5.0
     */
    public void setOnFailureExpression(@Nullable 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) {
        setOnFailureExpression(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() {
        super.onInit();
        BeanFactory beanFactory = getBeanFactory();
        if (beanFactory != null) {
            this.messagingTemplate.setBeanFactory(beanFactory);
        }

        if (this.onSuccessExpression == null
                && (this.successChannel != null || StringUtils.hasText(this.successChannelName))) {

            this.onSuccessExpression = DEFAULT_EXPRESSION;
        }

        if (this.onFailureExpression == null
                && (this.failureChannel != null || StringUtils.hasText(this.failureChannelName))) {

            this.onFailureExpression = DEFAULT_EXPRESSION;
        }
    }

    @Override
    protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
        try {
            Object result = callback.execute();
            if (this.onSuccessExpression != null) {
                evaluateSuccessExpression(message);
            }
            return result;
        } catch (RuntimeException e) {
            Exception actualException = unwrapExceptionIfNecessary(e);
            if (this.onFailureExpression != null) {
                Object evalResult = evaluateFailureExpression(message, actualException);
                if (this.returnFailureExpressionResult) {
                    return evalResult;
                }
            }
            if (!this.trapException) {
                if (e instanceof ThrowableHolderException) { // NOSONAR
                    throw (ThrowableHolderException) e;
                } else {
                    throw new ThrowableHolderException(actualException); // NOSONAR lost stack trace
                }
            }
            return null;
        }
    }

    private void evaluateSuccessExpression(Message<?> message) {
        Object evalResult;
        try {
            evalResult = this.onSuccessExpression.getValue(prepareEvaluationContextToUse(null), message);
        } catch (Exception e) {
            evalResult = e;
        }
        DestinationResolver<MessageChannel> channelResolver = getChannelResolver();
        if (this.successChannel == null && this.successChannelName != null && channelResolver != null) {
            this.successChannel = channelResolver.resolveDestination(this.successChannelName);
        }
        if (evalResult != null && this.successChannel != null) {
            AdviceMessage<?> resultMessage = new AdviceMessage<>(evalResult, message);
            this.messagingTemplate.send(this.successChannel, resultMessage);
        }
        if (evalResult instanceof Exception && this.propagateOnSuccessEvaluationFailures) {
            throw new ThrowableHolderException((Exception) evalResult);
        }
    }

    private Object evaluateFailureExpression(Message<?> message, Exception exception) {
        Object evalResult;
        try {
            evalResult = this.onFailureExpression.getValue(prepareEvaluationContextToUse(exception), message);
        } catch (Exception e) {
            evalResult = e;
            logger.error("Failure expression evaluation failed for " + message + ": " + e.getMessage());
        }
        DestinationResolver<MessageChannel> channelResolver = getChannelResolver();
        if (this.failureChannel == null && this.failureChannelName != null && channelResolver != null) {
            this.failureChannel = channelResolver.resolveDestination(this.failureChannelName);
        }
        if (evalResult != null && this.failureChannel != null) {
            MessagingException messagingException = new MessageHandlingExpressionEvaluatingAdviceException(message,
                    "Handler Failed", unwrapThrowableIfNecessary(exception), evalResult);
            ErrorMessage errorMessage = new ErrorMessage(messagingException);
            this.messagingTemplate.send(this.failureChannel, errorMessage);
        }
        return evalResult;
    }

    protected StandardEvaluationContext createEvaluationContext() {
        return ExpressionUtils.createStandardEvaluationContext(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 the {@link Exception} to use in the context.
     * @return The context.
     */
    private EvaluationContext prepareEvaluationContextToUse(Exception exception) {
        EvaluationContext evaluationContextToUse;
        if (exception != null) {
            evaluationContextToUse = createEvaluationContext();
            evaluationContextToUse.setVariable("exception", exception);
        } else {
            if (this.evaluationContext == null) {
                this.evaluationContext = 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;
        }

    }

}