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