diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Filter.java b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Filter.java
new file mode 100644
index 0000000000..9d61d15799
--- /dev/null
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Filter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2010 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a method is capable of playing the role of a Message Filter.
+ *
+ * A method annotated with @Filter may accept a parameter of type
+ * {@link org.springframework.integration.core.Message} or of the expected
+ * Message payload's type. Any type conversion supported by default or any
+ * Converters registered with the "integrationConversionService" bean will be
+ * applied to the Message payload if necessary. Header values can also be passed
+ * 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
+ * @since 2.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Filter {
+
+ String inputChannel() default "";
+
+ String outputChannel() default "";
+
+}
diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/FilterFactoryBean.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/FilterFactoryBean.java
index b7e3e0b535..e20239f7c8 100644
--- a/org.springframework.integration/src/main/java/org/springframework/integration/config/FilterFactoryBean.java
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/FilterFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2010 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,15 +53,17 @@ public class FilterFactoryBean extends AbstractMessageHandlerFactoryBean {
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
+ MessageSelector selector = null;
if (targetObject instanceof MessageSelector) {
- return this.createFilter((MessageSelector) targetObject);
+ selector = (MessageSelector) targetObject;
}
- if (StringUtils.hasText(targetMethodName)) {
- MessageSelector selector = new MethodInvokingSelector(targetObject, targetMethodName);
- return this.createFilter(selector);
+ else if (StringUtils.hasText(targetMethodName)) {
+ selector = new MethodInvokingSelector(targetObject, targetMethodName);
}
- throw new IllegalArgumentException("Filter must provide a target method" +
- " name if the 'ref' does not point to a MessageSelector instance.");
+ else {
+ selector = new MethodInvokingSelector(targetObject);
+ }
+ return this.createFilter(selector);
}
@Override
diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java
new file mode 100644
index 0000000000..2e2604e278
--- /dev/null
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2010 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 java.lang.reflect.Method;
+
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.integration.annotation.Filter;
+import org.springframework.integration.filter.MessageFilter;
+import org.springframework.integration.filter.MethodInvokingSelector;
+import org.springframework.integration.message.MessageHandler;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Post-processor for Methods annotated with {@link Filter @Filter}.
+ *
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor {
+
+ public FilterAnnotationPostProcessor(ListableBeanFactory beanFactory) {
+ super(beanFactory);
+ }
+
+
+ @Override
+ protected MessageHandler createHandler(Object bean, Method method, Filter annotation) {
+ Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()),
+ "The Filter annotation may only be applied to methods with a boolean return type.");
+ MethodInvokingSelector selector = new MethodInvokingSelector(bean, method);
+ MessageFilter filter = new MessageFilter(selector);
+ String outputChannelName = annotation.outputChannel();
+ if (StringUtils.hasText(outputChannelName)) {
+ filter.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
+ }
+ return filter;
+ }
+
+}
diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java
index 58651df129..cd4be0f222 100644
--- a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2010 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.
@@ -44,6 +44,7 @@ import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
+import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
@@ -57,7 +58,7 @@ import org.springframework.util.StringUtils;
/**
* A {@link BeanPostProcessor} implementation that processes method-level
- * messaging annotations such as @Transformer, @Splitter, and @Router.
+ * messaging annotations such as @Transformer, @Splitter, @Router, and @Filter.
*
* @author Mark Fisher
* @author Marius Bogoevici
@@ -87,11 +88,12 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
public void afterPropertiesSet() {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
- postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory));
+ postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory));
+ postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory));
- postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
+ postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory));
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/filter/MethodInvokingSelector.java b/org.springframework.integration/src/main/java/org/springframework/integration/filter/MethodInvokingSelector.java
index 285f45d979..e587934dee 100644
--- a/org.springframework.integration/src/main/java/org/springframework/integration/filter/MethodInvokingSelector.java
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/filter/MethodInvokingSelector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2010 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,7 @@ package org.springframework.integration.filter;
import java.lang.reflect.Method;
+import org.springframework.integration.annotation.Filter;
import org.springframework.integration.handler.MethodInvokingMessageProcessor;
import org.springframework.integration.selector.MessageSelector;
import org.springframework.util.Assert;
@@ -41,4 +42,8 @@ public class MethodInvokingSelector extends AbstractMessageProcessingSelector {
super(new MethodInvokingMessageProcessor(object, methodName));
}
+ public MethodInvokingSelector(Object object) {
+ super(new MethodInvokingMessageProcessor(object, Filter.class));
+ }
+
}
diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java
new file mode 100644
index 0000000000..aa97e087e3
--- /dev/null
+++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2002-2010 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 static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+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.core.Message;
+import org.springframework.integration.message.StringMessage;
+import org.springframework.integration.test.util.TestUtils;
+import org.springframework.integration.test.util.TestUtils.TestApplicationContext;
+
+/**
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public class FilterAnnotationPostProcessorTests {
+
+ private final TestApplicationContext context = TestUtils.createTestApplicationContext();
+
+ private final MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
+
+ private final DirectChannel inputChannel = new DirectChannel();
+
+ private final QueueChannel outputChannel = new QueueChannel();
+
+
+ @Before
+ public void init() {
+ context.registerChannel("input", inputChannel);
+ context.registerChannel("output", outputChannel);
+ postProcessor.setBeanFactory(context.getBeanFactory());
+ postProcessor.afterPropertiesSet();
+ }
+
+
+ @Test
+ public void filterAnnotationWithBooleanPrimitive() {
+ testValidFilter(new TestFilterWithBooleanPrimitive());
+ }
+
+ @Test
+ public void filterAnnotationWithBooleanWrapperClass() {
+ testValidFilter(new TestFilterWithBooleanWrapperClass());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void invalidMethodWithStringReturnType() {
+ Object filter = new TestFilterWithStringReturnType();
+ postProcessor.postProcessAfterInitialization(filter, "testFilter");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void invalidMethodWithVoidReturnType() {
+ Object filter = new TestFilterWithVoidReturnType();
+ postProcessor.postProcessAfterInitialization(filter, "testFilter");
+ }
+
+
+ private void testValidFilter(Object filter) {
+ postProcessor.postProcessAfterInitialization(filter, "testFilter");
+ context.refresh();
+ inputChannel.send(new StringMessage("good"));
+ Message> passed = outputChannel.receive(0);
+ assertNotNull(passed);
+ inputChannel.send(new StringMessage("bad"));
+ assertNull(outputChannel.receive(0));
+ context.stop();
+ }
+
+
+ @MessageEndpoint
+ @SuppressWarnings("unused")
+ private static class TestFilterWithBooleanPrimitive {
+
+ @Filter(inputChannel="input", outputChannel="output")
+ public boolean filter(String s) {
+ return !s.contains("bad");
+ }
+ }
+
+
+ @MessageEndpoint
+ @SuppressWarnings("unused")
+ private static class TestFilterWithBooleanWrapperClass {
+
+ @Filter(inputChannel="input", outputChannel="output")
+ public Boolean filter(String s) {
+ return !s.contains("bad");
+ }
+ }
+
+
+ @MessageEndpoint
+ @SuppressWarnings("unused")
+ private static class TestFilterWithStringReturnType {
+
+ @Filter(inputChannel="input", outputChannel="output")
+ public String filter(String s) {
+ return s;
+ }
+ }
+
+
+ @MessageEndpoint
+ @SuppressWarnings("unused")
+ private static class TestFilterWithVoidReturnType {
+
+ @Filter(inputChannel="input", outputChannel="output")
+ public void filter(String s) {
+ }
+ }
+
+}
diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/filter/FilterAnnotationMethodResolutionTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/filter/FilterAnnotationMethodResolutionTests.java
new file mode 100644
index 0000000000..73353aa462
--- /dev/null
+++ b/org.springframework.integration/src/test/java/org/springframework/integration/filter/FilterAnnotationMethodResolutionTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2010 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.filter;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.integration.annotation.Filter;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.config.FilterFactoryBean;
+import org.springframework.integration.core.Message;
+import org.springframework.integration.message.MessageBuilder;
+import org.springframework.integration.message.MessageHandler;
+
+/**
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public class FilterAnnotationMethodResolutionTests {
+
+ @Test
+ public void resolveAnnotatedMethod() throws Exception {
+ FilterFactoryBean factoryBean = new FilterFactoryBean();
+ factoryBean.setBeanFactory(new DefaultListableBeanFactory());
+ AnnotatedTestFilter filter = new AnnotatedTestFilter();
+ factoryBean.setTargetObject(filter);
+ MessageHandler handler = factoryBean.getObject();
+ QueueChannel replyChannel = new QueueChannel();
+ handler.handleMessage(MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build());
+ Message> result = replyChannel.receive(0);
+ assertNotNull(result);
+ assertTrue(filter.invokedCorrectMethod);
+ assertFalse(filter.invokedIncorrectMethod);
+ }
+
+
+ public static class AnnotatedTestFilter {
+
+ private volatile boolean invokedCorrectMethod;
+
+ private volatile boolean invokedIncorrectMethod;
+
+
+ public String notThisOne(String s) {
+ this.invokedIncorrectMethod = true;
+ return s;
+ }
+
+ public void norThisOne(String s) {
+ this.invokedIncorrectMethod = true;
+ }
+
+ public boolean andNotEvenThisOne(String s) {
+ this.invokedIncorrectMethod = true;
+ return true;
+ }
+
+ @Filter
+ public boolean thisOne(String s) {
+ this.invokedCorrectMethod = true;
+ return true;
+ }
+ }
+
+}