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; + } + } + +}