From c2d77a8c876af3835031762897ea5c61482d2ab8 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Mon, 31 Aug 2009 17:36:40 +0000 Subject: [PATCH] INT-777 Added support for EL-based filters. --- .../integration/config/FilterFactoryBean.java | 77 +++++++++++++++++++ .../integration/config/xml/FilterParser.java | 51 +++--------- .../AbstractMessageProcessingSelector.java | 48 ++++++++++++ .../filter/ExpressionEvaluatingSelector.java | 35 +++++++++ .../filter/MethodInvokingSelector.java | 20 +---- .../config/xml/spring-integration-2.0.xsd | 18 +++-- .../SpelFilterIntegrationTests-context.xml | 22 ++++++ .../filter/SpelFilterIntegrationTests.java | 64 +++++++++++++++ 8 files changed, 275 insertions(+), 60 deletions(-) create mode 100644 org.springframework.integration/src/main/java/org/springframework/integration/config/FilterFactoryBean.java create mode 100644 org.springframework.integration/src/main/java/org/springframework/integration/filter/AbstractMessageProcessingSelector.java create mode 100644 org.springframework.integration/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java create mode 100644 org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests-context.xml create mode 100644 org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests.java 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 new file mode 100644 index 0000000000..6a563ea53b --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/FilterFactoryBean.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2009 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; + +import org.springframework.integration.core.MessageChannel; +import org.springframework.integration.filter.ExpressionEvaluatingSelector; +import org.springframework.integration.filter.MessageFilter; +import org.springframework.integration.filter.MethodInvokingSelector; +import org.springframework.integration.message.MessageHandler; +import org.springframework.integration.selector.MessageSelector; +import org.springframework.util.StringUtils; + +/** + * Factory bean for creating a Message Filter. + * + * @author Mark Fisher + * @since 2.0 + */ +public class FilterFactoryBean extends AbstractMessageHandlerFactoryBean { + + private volatile MessageChannel discardChannel; + + private volatile Boolean throwExceptionOnRejection; + + + public void setDiscardChannel(MessageChannel discardChannel) { + this.discardChannel = discardChannel; + } + + public void setThrowExceptionOnRejection(Boolean throwExceptionOnRejection) { + this.throwExceptionOnRejection = throwExceptionOnRejection; + } + + @Override + MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) { + if (targetObject instanceof MessageSelector) { + return this.createFilter((MessageSelector) targetObject); + } + if (StringUtils.hasText(targetMethodName)) { + MessageSelector selector = new MethodInvokingSelector(targetObject, targetMethodName); + return this.createFilter(selector); + } + throw new IllegalArgumentException("Filter must provide a target method" + + " name if the 'ref' does not point to a MessageSelector instance."); + } + + @Override + MessageHandler createExpressionEvaluatingHandler(String expression) { + return this.createFilter(new ExpressionEvaluatingSelector(expression)); + } + + private MessageFilter createFilter(MessageSelector selector) { + MessageFilter filter = new MessageFilter(selector); + if (this.throwExceptionOnRejection != null) { + filter.setThrowExceptionOnRejection(this.throwExceptionOnRejection); + } + if (this.discardChannel != null) { + filter.setDiscardChannel(discardChannel); + } + return filter; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/FilterParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/FilterParser.java index 6f1fdced11..d315c3f6d3 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/FilterParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/FilterParser.java @@ -18,57 +18,30 @@ package org.springframework.integration.config.xml; import org.w3c.dom.Element; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.StringUtils; /** * Parser for the <filter/> element. * * @author Mark Fisher - * @author Oleg Zhurakousky */ -public class FilterParser extends AbstractConsumerEndpointParser { +public class FilterParser extends AbstractDelegatingConsumerEndpointParser { @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".filter.MessageFilter"); - - builder.addConstructorArgReference((String) this.parseSelector(element, parserContext)); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "discard-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "throw-exception-on-rejection"); - return builder; + String getFactoryBeanClassName() { + return IntegrationNamespaceUtils.BASE_PACKAGE + ".config.FilterFactoryBean"; } - private String parseSelector(Element element, ParserContext parserContext) { - BeanDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext); - String ref = null; - if (innerHandlerDefinition == null){ - ref = element.getAttribute("ref"); - if (!StringUtils.hasText(ref)) { - parserContext.getReaderContext().error("Either \"ref\" attribute or inner bean () definition of concrete implementation of " + - "this MessageFilter is required.", element); - return null; - } - } - String method = element.getAttribute("method"); - if (!StringUtils.hasText(method)) { - return ref; - } - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".filter.MethodInvokingSelector"); - if (innerHandlerDefinition != null){ - builder.addConstructorArgValue(innerHandlerDefinition); - } else { - builder.addConstructorArgReference(ref); - } - - builder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String"); - return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry()); + @Override + boolean hasDefaultOption() { + return false; + } + + @Override + void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "discard-channel"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "throw-exception-on-rejection"); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/filter/AbstractMessageProcessingSelector.java b/org.springframework.integration/src/main/java/org/springframework/integration/filter/AbstractMessageProcessingSelector.java new file mode 100644 index 0000000000..b083a2d28d --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/filter/AbstractMessageProcessingSelector.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2009 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 org.springframework.integration.core.Message; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.selector.MessageSelector; +import org.springframework.util.Assert; + +/** + * A base class for {@link MessageSelector} implementations that delegate to + * a {@link MessageProcessor}. + * + * @author Mark Fisher + */ +abstract class AbstractMessageProcessingSelector implements MessageSelector { + + private final MessageProcessor messageProcessor; + + + public AbstractMessageProcessingSelector(MessageProcessor messageProcessor) { + Assert.notNull(messageProcessor, "messageProcessor must not be null"); + this.messageProcessor = messageProcessor; + } + + + public final boolean accept(Message message) { + Object result = this.messageProcessor.processMessage(message); + Assert.notNull(result, "result must not be null"); + Assert.isAssignable(Boolean.class, result.getClass(), "a boolean result is required"); + return (Boolean) result; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java b/org.springframework.integration/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java new file mode 100644 index 0000000000..574d968081 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2009 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 org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; +import org.springframework.integration.selector.MessageSelector; + +/** + * A {@link MessageSelector} implementation that evaluates a SpEL expression. + * The evaluation result of the expression must be a boolean value. + * + * @author Mark Fisher + * @since 2.0 + */ +public class ExpressionEvaluatingSelector extends AbstractMessageProcessingSelector { + + public ExpressionEvaluatingSelector(String expression) { + super(new ExpressionEvaluatingMessageProcessor(expression)); + } + +} 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 ecc71730b9..db956cd490 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-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package org.springframework.integration.filter; import java.lang.reflect.Method; -import org.springframework.integration.core.Message; import org.springframework.integration.handler.MessageMappingMethodInvoker; import org.springframework.integration.selector.MessageSelector; import org.springframework.util.Assert; @@ -28,13 +27,10 @@ import org.springframework.util.Assert; * * @author Mark Fisher */ -public class MethodInvokingSelector implements MessageSelector { - - private final MessageMappingMethodInvoker invoker; - +public class MethodInvokingSelector extends AbstractMessageProcessingSelector { public MethodInvokingSelector(Object object, Method method) { - this.invoker = new MessageMappingMethodInvoker(object, method); + super(new MessageMappingMethodInvoker(object, method)); Class returnType = method.getReturnType(); Assert.isTrue(boolean.class.isAssignableFrom(returnType) || Boolean.class.isAssignableFrom(returnType), @@ -42,15 +38,7 @@ public class MethodInvokingSelector implements MessageSelector { } public MethodInvokingSelector(Object object, String methodName) { - this.invoker = new MessageMappingMethodInvoker(object, methodName); - } - - - public boolean accept(Message message) { - Object result = this.invoker.processMessage(message); - Assert.notNull(result, "result must not be null"); - Assert.isAssignable(Boolean.class, result.getClass(), "a boolean result is required"); - return (Boolean) result; + super(new MessageMappingMethodInvoker(object, methodName)); } } diff --git a/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd b/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd index 08d1c31283..785f2c7a21 100644 --- a/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd +++ b/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd @@ -1033,15 +1033,23 @@ - Defines a Filter. + Defines a Filter. - - - + + + + + + + + + + + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests-context.xml b/org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests-context.xml new file mode 100644 index 0000000000..22e7cd5f69 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests.java new file mode 100644 index 0000000000..e2368e74c6 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/filter/SpelFilterIntegrationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2009 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.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.channel.PollableChannel; +import org.springframework.integration.core.MessageChannel; +import org.springframework.integration.message.GenericMessage; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class SpelFilterIntegrationTests { + + @Autowired @Qualifier("input") + private MessageChannel input; + + @Autowired @Qualifier("positives") + private PollableChannel positives; + + @Autowired @Qualifier("discards") + private PollableChannel discards; + + + @Test + public void filter() { + this.input.send(new GenericMessage(1)); + this.input.send(new GenericMessage(0)); + this.input.send(new GenericMessage(99)); + this.input.send(new GenericMessage(-99)); + assertEquals(new Integer(1), positives.receive(0).getPayload()); + assertEquals(new Integer(99), positives.receive(0).getPayload()); + assertEquals(new Integer(0), discards.receive(0).getPayload()); + assertEquals(new Integer(-99), discards.receive(0).getPayload()); + assertNull(positives.receive(0)); + assertNull(discards.receive(0)); + } + +}