From 3301da4157123264df1e0c7db2d4b3095edd7193 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Tue, 3 Jan 2017 18:35:34 -0500 Subject: [PATCH] INT-4203: Expression RH Advice Improvements JIRA: https://jira.spring.io/browse/INT-4203 Add channel names for easier use in DSL. Add documentation (boot) example. Normalize Expression Setters; add Javadocs Polishing - SPR-15091 Tiny code style polishing Conflicts: spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java src/reference/asciidoc/handler-advice.adoc spring-integration-core/src/test/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdviceTests.java * Restore removed methods in the `ExpressionEvaluatingRequestHandlerAdvice` and deprecate them * Remove methods in the `ExpressionEvaluatingRequestHandlerAdvice` introduced since `5.0` --- ...ressionEvaluatingRequestHandlerAdvice.java | 109 +++++++++++++++-- .../integration/message/AdviceMessage.java | 10 +- .../advice/AdvisedMessageHandlerTests.java | 20 ++-- ...boundChannelAdapterParserTests-context.xml | 2 +- src/reference/asciidoc/handler-advice.adoc | 113 +++++++++++++----- 5 files changed, 198 insertions(+), 56 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java index 65b039c8f3..86c1ec3d47 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -18,7 +18,6 @@ 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; @@ -41,6 +40,7 @@ import org.springframework.util.Assert; * * @author Gary Russell * @author Artem Bilan + * * @since 2.2 * */ @@ -50,10 +50,14 @@ public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHan 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; @@ -64,34 +68,106 @@ public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHan private volatile EvaluationContext evaluationContext; + /** + * Set the expression to evaluate against the message after a successful + * handler invocation. + * @param onSuccessExpression the SpEL expression. + * @deprecated since 4.3.7 in favor of {@link #setOnSuccessExpressionString(String)} + */ + @Deprecated public void setOnSuccessExpression(String onSuccessExpression) { - Assert.notNull(onSuccessExpression, "'onSuccessExpression' must not be null"); - this.onSuccessExpression = new SpelExpressionParser().parseExpression(onSuccessExpression); + setOnSuccessExpressionString(onSuccessExpression); } + /** + * 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) { + Assert.notNull(onSuccessExpression, "'onSuccessExpression' must not be null"); + this.onSuccessExpression = EXPRESSION_PARSER.parseExpression(onSuccessExpression); + } + + /** + * Set the expression to evaluate against the message after a successful + * handler invocation. + * @param onSuccessExpression the SpEL expression. + */ 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. + * @deprecated since 4.3.7 in favor of {@link #setOnFailureExpressionString(String)} + */ + @Deprecated public void setOnFailureExpression(String onFailureExpression) { - Assert.notNull(onFailureExpression, "'onFailureExpression' must not be null"); - this.onFailureExpression = new SpelExpressionParser().parseExpression(onFailureExpression); + setOnFailureExpressionString(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 4.3.7 + */ + public void setOnFailureExpressionString(String onFailureExpression) { + Assert.notNull(onFailureExpression, "'onFailureExpression' must not be null"); + this.onFailureExpression = 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} + * @param onFailureExpression the SpEL expression. + */ 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) { - Assert.notNull(successChannel, "'successChannel' must not be null"); 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) { - Assert.notNull(failureChannel, "'failureChannel' must not be null"); 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. @@ -104,7 +180,6 @@ public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHan /** * 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) { @@ -112,10 +187,12 @@ public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHan } /** - * 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. + * 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; @@ -163,6 +240,9 @@ public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHan 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(evalResult, message); this.messagingTemplate.send(this.successChannel, resultMessage); @@ -181,6 +261,9 @@ public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHan 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); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/AdviceMessage.java b/spring-integration-core/src/main/java/org/springframework/integration/message/AdviceMessage.java index 67dd3ec339..41e1575ac5 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/message/AdviceMessage.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/AdviceMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -53,4 +53,12 @@ public class AdviceMessage extends GenericMessage { return this.inputMessage; } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(super.toString()); + builder.setLength(builder.length() - 1); + builder.append(", inputMessage=").append(this.inputMessage.toString()).append("]"); + return builder.toString(); + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/advice/AdvisedMessageHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/advice/AdvisedMessageHandlerTests.java index c5e3eb2652..0f9d036de1 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/advice/AdvisedMessageHandlerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/advice/AdvisedMessageHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -150,8 +150,8 @@ public class AdvisedMessageHandlerTests { advice.setBeanFactory(mock(BeanFactory.class)); advice.setSuccessChannel(successChannel); advice.setFailureChannel(failureChannel); - advice.setOnSuccessExpression("'foo'"); - advice.setOnFailureExpression("'bar:' + #exception.message"); + advice.setOnSuccessExpressionString("'foo'"); + advice.setOnFailureExpressionString("'bar:' + #exception.message"); List adviceChain = new ArrayList(); adviceChain.add(advice); @@ -244,8 +244,8 @@ public class AdvisedMessageHandlerTests { advice.setBeanFactory(mock(BeanFactory.class)); advice.setSuccessChannel(successChannel); advice.setFailureChannel(failureChannel); - advice.setOnSuccessExpression("1/0"); - advice.setOnFailureExpression("1/0"); + advice.setOnSuccessExpressionString("1/0"); + advice.setOnFailureExpressionString("1/0"); List adviceChain = new ArrayList(); adviceChain.add(advice); @@ -308,8 +308,8 @@ public class AdvisedMessageHandlerTests { advice.setBeanFactory(mock(BeanFactory.class)); advice.setSuccessChannel(successChannel); advice.setFailureChannel(failureChannel); - advice.setOnSuccessExpression("1/0"); - advice.setOnFailureExpression("1/0"); + advice.setOnSuccessExpressionString("1/0"); + advice.setOnFailureExpressionString("1/0"); List adviceChain = new ArrayList(); adviceChain.add(advice); @@ -752,7 +752,7 @@ public class AdvisedMessageHandlerTests { ExpressionEvaluatingRequestHandlerAdvice expressionAdvice = new ExpressionEvaluatingRequestHandlerAdvice(); expressionAdvice.setBeanFactory(mock(BeanFactory.class)); // MessagingException / RuntimeException - expressionAdvice.setOnFailureExpression("#exception.cause.message"); + expressionAdvice.setOnFailureExpressionString("#exception.cause.message"); expressionAdvice.setReturnFailureExpressionResult(true); final AtomicInteger outerCounter = new AtomicInteger(); adviceChain.add(new AbstractRequestHandlerAdvice() { @@ -803,7 +803,7 @@ public class AdvisedMessageHandlerTests { ExpressionEvaluatingRequestHandlerAdvice expressionAdvice = new ExpressionEvaluatingRequestHandlerAdvice(); expressionAdvice.setBeanFactory(mock(BeanFactory.class)); - expressionAdvice.setOnFailureExpression("#exception.message"); + expressionAdvice.setOnFailureExpressionString("#exception.message"); expressionAdvice.setFailureChannel(errors); adviceChain.add(new RequestHandlerRetryAdvice()); @@ -891,7 +891,7 @@ public class AdvisedMessageHandlerTests { ExpressionEvaluatingRequestHandlerAdvice expressionAdvice = new ExpressionEvaluatingRequestHandlerAdvice(); expressionAdvice.setBeanFactory(mock(BeanFactory.class)); - expressionAdvice.setOnFailureExpression("'foo'"); + expressionAdvice.setOnFailureExpressionString("'foo'"); expressionAdvice.setFailureChannel(errors); Throwable theThrowable = new Throwable("foo"); diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml index abdcbcfd97..7fea9b0dbc 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml @@ -115,7 +115,7 @@ directory="test"> - + diff --git a/src/reference/asciidoc/handler-advice.adoc b/src/reference/asciidoc/handler-advice.adoc index 685e2b62d6..b76e65a969 100644 --- a/src/reference/asciidoc/handler-advice.adoc +++ b/src/reference/asciidoc/handler-advice.adoc @@ -171,18 +171,18 @@ It also adds an `ExponentialBackoffPolicy` where the first retry waits 1 second, - - - - - - - - - - - - + + + + + + + + + + + + 27.058 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=...] @@ -214,7 +214,7 @@ Starting with _version 4.0_, the above configuration can be greatly simplified w - + ---- @@ -225,9 +225,9 @@ You can also define the advice directly within the chain: ---- - - - + + + ---- @@ -391,6 +391,57 @@ When an exception is thrown in the scope of the advice, by default, that excepti `failureExpression` is evaluated. If you wish to suppress throwing the exception, set the `trapException` property to `true`. +.Example - Configuring the Advice with Java DSL +[source, java] +---- +@SpringBootApplication +public class EerhaApplication { + + public static void main(String[] args) { + ConfigurableApplicationContext context = SpringApplication.run(EerhaApplication.class, args); + MessageChannel in = context.getBean("advised.input", MessageChannel.class); + in.send(new GenericMessage<>("good")); + in.send(new GenericMessage<>("bad")); + context.close(); + } + + @Bean + public IntegrationFlow advised() { + return f -> f.handle((GenericHandler) (payload, headers) -> { + if (payload.equals("good")) { + return null; + } + else { + throw new RuntimeException("some failure"); + } + }, c -> c.advice(expressionAdvice())); + } + + @Bean + public Advice expressionAdvice() { + ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice(); + advice.setSuccessChannelName("success.input"); + advice.setOnSuccessExpression("payload + ' was successful'"); + advice.setFailureChannelName("failure.input"); + advice.setOnFailureExpression( + "payload + ' was bad, with reason: ' + #exception.cause.message"); + advice.setTrapException(true); + return advice; + } + + @Bean + public IntegrationFlow success() { + return f -> f.handle(System.out::println); + } + + @Bean + public IntegrationFlow failure() { + return f -> f.handle(System.out::println); + } + +} +---- + [[custom-advice]] ==== Custom Advice Classes @@ -510,11 +561,11 @@ An example with the discard being performed after the advice is shown below. @MessageEndpoint public class MyAdvisedFilter { - @Filter(inputChannel="input", outputChannel="output", - adviceChain="adviceChain", discardWithinAdvice="false") - public boolean filter(String s) { - return s.contains("good"); - } + @Filter(inputChannel="input", outputChannel="output", + adviceChain="adviceChain", discardWithinAdvice="false") + public boolean filter(String s) { + return s.contains("good"); + } } ---- @@ -592,16 +643,16 @@ For convenience, the `MetadataStoreSelector` options are configurable directly o [source,xml] ---- - endpoint="" <2> - selector="" <3> - discard-channel="" <4> - metadata-store="" <5> - key-strategy="" <6> - key-expression="" <7> - value-strategy="" <8> - value-expression="" <9> - throw-exception-on-rejection="" /> <10> + id="" <1> + endpoint="" <2> + selector="" <3> + discard-channel="" <4> + metadata-store="" <5> + key-strategy="" <6> + key-expression="" <7> + value-strategy="" <8> + value-expression="" <9> + throw-exception-on-rejection="" /> <10> ---- <1> The id of the `IdempotentReceiverInterceptor` bean.