From 573c6929578136c007f93e5d1704398a940c781c Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Tue, 16 Apr 2013 11:29:20 -0400 Subject: [PATCH] INT-2994 Advice Chain Config via Annotations Allow configuration of request handler advice chain using - ServiceActivator - Filter - Splitter - Transformer annotations. Also, with splitter, allow setting discardWithinAdvice (See INT-2938). INT-2994 Polishing: PR Comments - Change adviceChain attribute to an array - Use a boolean for the discardWithinAdvice attribute INT-2994 Handler Advice Doc Polishing Add a paragraph about Advice Order. --- .../integration/annotation/Filter.java | 9 +- .../annotation/ServiceActivator.java | 7 +- .../integration/annotation/Splitter.java | 7 +- .../integration/annotation/Transformer.java | 7 +- ...AbstractMethodAnnotationPostProcessor.java | 46 +++++- .../FilterAnnotationPostProcessor.java | 4 +- .../AnnotatedTestServiceWithAdvice.java | 39 +++++ .../FilterAnnotationPostProcessorTests.java | 145 +++++++++++++++++- ...MessagingAnnotationPostProcessorTests.java | 26 +++- .../simpleAnnotatedEndpointTests.xml | 11 +- .../integration/test/util/TestUtils.java | 9 +- src/reference/docbook/configuration.xml | 3 + src/reference/docbook/filter.xml | 4 + src/reference/docbook/handler-advice.xml | 42 +++++ src/reference/docbook/splitter.xml | 3 + src/reference/docbook/transformer.xml | 3 + src/reference/docbook/whats-new.xml | 7 + 17 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedTestServiceWithAdvice.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java index a70f675ef4..0be38ec475 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -33,8 +33,9 @@ import java.lang.annotation.Target; * as Message parameters by using the {@link Header @Header} parameter annotation. *

* The return type of the annotated method must be a boolean (or Boolean). - * + * * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ @Target(ElementType.METHOD) @@ -46,4 +47,8 @@ public @interface Filter { String outputChannel() default ""; + String[] adviceChain() default {}; + + boolean discardWithinAdvice() default true; + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java index 443ca46373..0229c9d217 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -36,8 +36,9 @@ import java.lang.annotation.Target; * Return values from the annotated method may be of any type. If the return * value is not a Message, a reply Message will be created with that object * as its payload. - * + * * @author Mark Fisher + * @author Gary Russell */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -49,4 +50,6 @@ public @interface ServiceActivator { String outputChannel() default ""; + String[] adviceChain() default {}; + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java index 3e76583f63..9054227726 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -36,8 +36,9 @@ import java.lang.annotation.Target; * Return values from the annotated method may be either a Collection or Array * with elements of any type. If the type is not a Message, each will be used * as the payload for creating a new Message. - * + * * @author Mark Fisher + * @author Gary Russell */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -48,4 +49,6 @@ public @interface Splitter { String outputChannel() default ""; + String[] adviceChain() default {}; + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java index d3bcb47ca8..f536fb5da7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -26,8 +26,9 @@ import java.lang.annotation.Target; /** * Indicates that a method is capable of transforming a message, message header, * or message payload. - * + * * @author Mark Fisher + * @author Gary Russell */ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @@ -39,4 +40,6 @@ public @interface Transformer { String outputChannel() default ""; + String[] adviceChain() default {}; + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java index 140d60cc09..4b3e52b0af 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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,6 +18,11 @@ package org.springframework.integration.config.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.aopalliance.aop.Advice; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -31,6 +36,7 @@ import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.SubscribableChannel; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.integration.support.channel.ChannelResolver; import org.springframework.util.Assert; @@ -40,11 +46,14 @@ import org.springframework.util.StringUtils; * Base class for Method-level annotation post-processors. * * @author Mark Fisher + * @author Gary Russell */ public abstract class AbstractMethodAnnotationPostProcessor implements MethodAnnotationPostProcessor { private static final String INPUT_CHANNEL_ATTRIBUTE = "inputChannel"; + private static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain"; + protected final BeanFactory beanFactory; @@ -60,6 +69,7 @@ public abstract class AbstractMethodAnnotationPostProcessor 0) { + if (!(handler instanceof AbstractReplyProducingMessageHandler)) { + throw new IllegalArgumentException("Cannot apply advice chain to " + handler.getClass().getName()); + } + List adviceChain = new ArrayList(); + for (String adviceChainName : adviceChainNames) { + Object adviceChainBean = this.beanFactory.getBean(adviceChainName); + if (adviceChainBean instanceof Advice) { + adviceChain.add((Advice) adviceChainBean); + } + else if (adviceChainBean instanceof Advice[]) { + for (Advice advice : (Advice[]) adviceChainBean) { + adviceChain.add(advice); + } + } + else if (adviceChainBean instanceof Collection) { + @SuppressWarnings("unchecked") + Collection adviceChainEntries = (Collection) adviceChainBean; + for (Advice advice : adviceChainEntries) { + adviceChain.add(advice); + } + } + else { + throw new IllegalArgumentException("Invalid advice chain type:" + + adviceChainName.getClass().getName() + " for bean '" + beanName + "'"); + } + } + ((AbstractReplyProducingMessageHandler) handler).setAdviceChain(adviceChain); + } + } + protected boolean shouldCreateEndpoint(T annotation) { return (StringUtils.hasText((String) AnnotationUtils.getValue(annotation, INPUT_CHANNEL_ATTRIBUTE))); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java index bce6e6ac0a..d400c9b9b6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -30,6 +30,7 @@ import org.springframework.util.StringUtils; * Post-processor for Methods annotated with {@link Filter @Filter}. * * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { @@ -49,6 +50,7 @@ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostP if (StringUtils.hasText(outputChannelName)) { filter.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName)); } + filter.setDiscardWithinAdvice(annotation.discardWithinAdvice()); return filter; } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedTestServiceWithAdvice.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedTestServiceWithAdvice.java new file mode 100644 index 0000000000..669be3c6b6 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedTestServiceWithAdvice.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 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.config.annotation; + +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.endpoint.annotation.TestService; + +/** + * @author Gary Russell + */ +@MessageEndpoint +public class AnnotatedTestServiceWithAdvice implements TestService { + + @ServiceActivator(inputChannel="inputChannel", outputChannel="outputChannel") + public String sayHello(String name) { + return "hello " + name; + } + + @ServiceActivator(inputChannel="advisedIn", outputChannel="advisedOut", adviceChain="advice") + public String sayHelloWithAdvice(String name) { + return "hello " + name; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java index e903741698..2a236c09f6 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -16,8 +16,15 @@ package org.springframework.integration.config.annotation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; import org.junit.Before; import org.junit.Test; @@ -27,12 +34,15 @@ import org.springframework.integration.annotation.Filter; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.test.util.TestUtils.TestApplicationContext; /** * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ public class FilterAnnotationPostProcessorTests { @@ -45,7 +55,6 @@ public class FilterAnnotationPostProcessorTests { private final QueueChannel outputChannel = new QueueChannel(); - @Before public void init() { context.registerChannel("input", inputChannel); @@ -60,6 +69,99 @@ public class FilterAnnotationPostProcessorTests { testValidFilter(new TestFilterWithBooleanPrimitive()); } + @Test + public void filterAnnotationWithAdviceDiscardWithin() { + TestAdvice advice = new TestAdvice(); + context.registerBean("adviceChain", advice); + testValidFilter(new TestFilterWithAdviceDiscardWithin()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0)); + assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + + @Test + public void filterAnnotationWithAdviceDiscardWithinTwice() { + TestAdvice advice1 = new TestAdvice(); + TestAdvice advice2 = new TestAdvice(); + context.registerBean("adviceChain1", advice1); + context.registerBean("adviceChain2", advice2); + testValidFilter(new TestFilterWithAdviceDiscardWithinTwice()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + List adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class); + assertEquals(2, adviceList.size()); + assertSame(advice1, adviceList.get(0)); + assertSame(advice2, adviceList.get(1)); + assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + + @Test + public void filterAnnotationWithAdviceDiscardWithout() { + TestAdvice advice = new TestAdvice(); + context.registerBean("adviceChain", advice); + testValidFilter(new TestFilterWithAdviceDiscardWithout()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0)); + assertFalse(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + + @Test + public void filterAnnotationWithAdviceArray() { + TestAdvice advice = new TestAdvice(); + context.registerBean("adviceChain", new TestAdvice[] {advice}); + testValidFilter(new TestFilterWithAdviceDiscardWithin()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0)); + assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + + @Test + public void filterAnnotationWithAdviceArrayTwice() { + TestAdvice advice1 = new TestAdvice(); + TestAdvice advice2 = new TestAdvice(); + context.registerBean("adviceChain1", new TestAdvice[] {advice1, advice2}); + TestAdvice advice3 = new TestAdvice(); + TestAdvice advice4 = new TestAdvice(); + context.registerBean("adviceChain2", new TestAdvice[] {advice3, advice4}); + testValidFilter(new TestFilterWithAdviceDiscardWithinTwice()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + List adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class); + assertEquals(4, adviceList.size()); + assertSame(advice1, adviceList.get(0)); + assertSame(advice2, adviceList.get(1)); + assertSame(advice3, adviceList.get(2)); + assertSame(advice4, adviceList.get(3)); + assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + + @Test + public void filterAnnotationWithAdviceCollection() { + TestAdvice advice = new TestAdvice(); + context.registerBean("adviceChain", Arrays.asList(new TestAdvice[] {advice})); + testValidFilter(new TestFilterWithAdviceDiscardWithin()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0)); + assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + + @Test + public void filterAnnotationWithAdviceCollectionTwice() { + TestAdvice advice1 = new TestAdvice(); + TestAdvice advice2 = new TestAdvice(); + context.registerBean("adviceChain1", new TestAdvice[] {advice1, advice2}); + TestAdvice advice3 = new TestAdvice(); + TestAdvice advice4 = new TestAdvice(); + context.registerBean("adviceChain2", new TestAdvice[] {advice3, advice4}); + testValidFilter(new TestFilterWithAdviceDiscardWithinTwice()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); + List adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class); + assertEquals(4, adviceList.size()); + assertSame(advice1, adviceList.get(0)); + assertSame(advice2, adviceList.get(1)); + assertSame(advice3, adviceList.get(2)); + assertSame(advice4, adviceList.get(3)); + assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class)); + } + @Test public void filterAnnotationWithBooleanWrapperClass() { testValidFilter(new TestFilterWithBooleanWrapperClass()); @@ -91,7 +193,6 @@ public class FilterAnnotationPostProcessorTests { @MessageEndpoint - @SuppressWarnings("unused") private static class TestFilterWithBooleanPrimitive { @Filter(inputChannel="input", outputChannel="output") @@ -100,9 +201,35 @@ public class FilterAnnotationPostProcessorTests { } } + @MessageEndpoint + private static class TestFilterWithAdviceDiscardWithin { + + @Filter(inputChannel="input", outputChannel="output", adviceChain="adviceChain") + public boolean filter(String s) { + return !s.contains("bad"); + } + } + + @MessageEndpoint + private static class TestFilterWithAdviceDiscardWithinTwice { + + @Filter(inputChannel="input", outputChannel="output", adviceChain={"adviceChain1", "adviceChain2"}) + public boolean filter(String s) { + return !s.contains("bad"); + } + } + + @MessageEndpoint + private static class TestFilterWithAdviceDiscardWithout { + + @Filter(inputChannel="input", outputChannel="output", + adviceChain="adviceChain", discardWithinAdvice=false) + public boolean filter(String s) { + return !s.contains("bad"); + } + } @MessageEndpoint - @SuppressWarnings("unused") private static class TestFilterWithBooleanWrapperClass { @Filter(inputChannel="input", outputChannel="output") @@ -113,7 +240,6 @@ public class FilterAnnotationPostProcessorTests { @MessageEndpoint - @SuppressWarnings("unused") private static class TestFilterWithStringReturnType { @Filter(inputChannel="input", outputChannel="output") @@ -124,7 +250,6 @@ public class FilterAnnotationPostProcessorTests { @MessageEndpoint - @SuppressWarnings("unused") private static class TestFilterWithVoidReturnType { @Filter(inputChannel="input", outputChannel="output") @@ -132,4 +257,12 @@ public class FilterAnnotationPostProcessorTests { } } + public static class TestAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + return callback.execute(); + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java index 2dd1aa2231..59f9d23d27 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -37,6 +37,7 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.endpoint.AbstractEndpoint; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; @@ -46,6 +47,7 @@ import org.springframework.integration.test.util.TestUtils.TestApplicationContex /** * @author Mark Fisher + * @author Gary Russell */ public class MessagingAnnotationPostProcessorTests { @@ -82,9 +84,16 @@ public class MessagingAnnotationPostProcessorTests { context.start(); MessageChannel inputChannel = (MessageChannel) context.getBean("inputChannel"); PollableChannel outputChannel = (PollableChannel) context.getBean("outputChannel"); - inputChannel.send(new GenericMessage("world")); + GenericMessage messageToSend = new GenericMessage("world"); + inputChannel.send(messageToSend); Message message = outputChannel.receive(1000); assertEquals("hello world", message.getPayload()); + + inputChannel = context.getBean("advisedIn", MessageChannel.class); + outputChannel = context.getBean("advisedOut", PollableChannel.class); + inputChannel.send(messageToSend); + message = outputChannel.receive(1000); + assertEquals("hello world advised", message.getPayload()); context.stop(); } @@ -301,7 +310,7 @@ public class MessagingAnnotationPostProcessorTests { private String messageText; - private CountDownLatch latch; + private final CountDownLatch latch; public OutboundOnlyTestBean(CountDownLatch latch) { @@ -313,7 +322,6 @@ public class MessagingAnnotationPostProcessorTests { } @ServiceActivator(inputChannel="testChannel") - @SuppressWarnings("unused") public void countdown(String input) { this.messageText = input; latch.countDown(); @@ -343,7 +351,6 @@ public class MessagingAnnotationPostProcessorTests { @MessageEndpoint private static class ServiceActivatorAnnotatedBean { - @SuppressWarnings("unused") @ServiceActivator(inputChannel="inputChannel") public String test(String s) { return s + s; @@ -355,11 +362,18 @@ public class MessagingAnnotationPostProcessorTests { @MessageEndpoint private static class TransformerAnnotationTestBean { - @SuppressWarnings("unused") @Transformer(inputChannel="inputChannel", outputChannel="outputChannel") public String transformBefore(String input) { return input.toUpperCase(); } } + public static class ServiceActivatorAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + return callback.execute() + " advised"; + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/simpleAnnotatedEndpointTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/simpleAnnotatedEndpointTests.xml index 85660a3fad..7670cd9125 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/simpleAnnotatedEndpointTests.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/simpleAnnotatedEndpointTests.xml @@ -15,6 +15,15 @@ - + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/test/util/TestUtils.java b/spring-integration-core/src/test/java/org/springframework/integration/test/util/TestUtils.java index 2aca8b709e..202412d28f 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/test/util/TestUtils.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/test/util/TestUtils.java @@ -55,6 +55,7 @@ import org.springframework.util.StringUtils; * @author Iwein Fuld * @author Oleg Zhurakousky * @author Artem Bilan + * @author Gary Russell */ public abstract class TestUtils { @@ -142,11 +143,15 @@ public abstract class TestUtils { "channel name has already been set with a conflicting value"); } } - registerBean(channelName, channel, this); + TestUtils.registerBean(channelName, channel, this); } public void registerEndpoint(String endpointName, AbstractEndpoint endpoint) { - registerBean(endpointName, endpoint, this); + TestUtils.registerBean(endpointName, endpoint, this); + } + + public void registerBean(String beanName, Object bean) { + TestUtils.registerBean(beanName, bean, this); } } diff --git a/src/reference/docbook/configuration.xml b/src/reference/docbook/configuration.xml index afb41ce44a..4a7409ef1a 100644 --- a/src/reference/docbook/configuration.xml +++ b/src/reference/docbook/configuration.xml @@ -326,6 +326,9 @@ public class FooService { still available. For more detail see . + + Also see . +

diff --git a/src/reference/docbook/filter.xml b/src/reference/docbook/filter.xml index 502a717d37..fbe510dd5d 100644 --- a/src/reference/docbook/filter.xml +++ b/src/reference/docbook/filter.xml @@ -164,6 +164,10 @@ The filter can be either referenced explicitly from XML or, if the @MessageEndpoint annotation is defined on the class, detected automatically through classpath scanning. + + + Also see . +
diff --git a/src/reference/docbook/handler-advice.xml b/src/reference/docbook/handler-advice.xml index e6f3100af0..91fb6b1175 100644 --- a/src/reference/docbook/handler-advice.xml +++ b/src/reference/docbook/handler-advice.xml @@ -458,6 +458,13 @@ protected abstract Object doInvoke(ExecutionCallback callback, Object target, Me +
+ Other Advice Chain Elements + + While the abstract class mentioned above is provided as a convenience, you can add any + Advice to the chain, including a transaction advice. + +
Advising Filters @@ -474,4 +481,39 @@ protected abstract Object doInvoke(ExecutionCallback callback, Object target, Me (or exception) occurs after the advice chain is called.
+
+ Advising Endpoints Using Annotations + + When configuring certain endpoints using annotations (@Filter, @ServiceActivator, + @Splitter, and @Transformer), you can supply a bean name for the advice + chain in the adviceChain attribute. In addition, the @Filter annotation + also has the discardWithinAdvice attribute, which can be used to configure the discard + behavior as discussed in . An example with the discard being + performed after the advice is shown below. + + +
+
+ Ordering Advices within an Advice Chain + + Advice classes are "around" advices and are applied in a nested fashion. The first advice is the + outermost, the last advice the innermost (closest to the handler being advised). It is important + to put the advice classes in the correct order to achieve the functionality you desire. + + + For example, let's say you want to add a retry advice and a transaction advice. + You may want to place the retry advice advice first, followed by the transaction advice. + Then, each retry will be performed in a new transaction. On the other hand, if you want all the attempts, + and any recovery operations (in the retry RecoveryCallback), to be scoped within + the transaction, you would put the transaction advice first. + +
diff --git a/src/reference/docbook/splitter.xml b/src/reference/docbook/splitter.xml index d2d7d6d31e..11aa0f95ba 100644 --- a/src/reference/docbook/splitter.xml +++ b/src/reference/docbook/splitter.xml @@ -161,6 +161,9 @@ List<LineItem> extractItems(Order order) { return order.getItems() } + + Also see . + diff --git a/src/reference/docbook/transformer.xml b/src/reference/docbook/transformer.xml index 8bd2394f4b..de076a877a 100644 --- a/src/reference/docbook/transformer.xml +++ b/src/reference/docbook/transformer.xml @@ -350,6 +350,9 @@ Order generateOrder(String productId, @Header("customerName") String customer) { return new Order(productId, customer); } + + Also see . + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 40e8392d9d..c4ea63eee9 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -92,6 +92,13 @@ be performed after the advice chain completes. See . +
+ Advising Endpoints using Annotations + + Request Handler Advice Chains can now be configured using annotations. See + . + +
ObjectToStringTransformer Improvements