diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/RouterFactoryBean.java index ab9e8aab60..ae49a9ddd5 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/RouterFactoryBean.java @@ -99,13 +99,14 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { MethodInvokingRouter router = (StringUtils.hasText(targetMethodName)) ? new MethodInvokingRouter(targetObject, targetMethodName) : new MethodInvokingRouter(targetObject); - if (this.channelResolver != null) { - router.setChannelResolver(this.channelResolver); - } return router; } private AbstractMessageRouter configureRouter(AbstractMessageRouter router) { + if (this.channelResolver != null && + router instanceof AbstractChannelNameResolvingMessageRouter) { + ((AbstractChannelNameResolvingMessageRouter) router).setChannelResolver(this.channelResolver); + } if (this.defaultOutputChannel != null) { router.setDefaultOutputChannel(this.defaultOutputChannel); } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java new file mode 100644 index 0000000000..d16b34df42 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java @@ -0,0 +1,60 @@ +/* + * 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.xml; + +import java.util.List; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.xml.DomUtils; + +/** + * Base parser for routers that create instances that are subclasses of AbstractChannelNameResolvingMessageRouter. + * + * @author Mark Fisher + */ +public abstract class AbstractChannelNameResolvingRouterParser extends AbstractRouterParser { + + @Override + protected final BeanDefinition parseRouter(Element element, ParserContext parserContext) { + BeanDefinition beanDefinition = this.doParseRouter(element, parserContext); + if (beanDefinition != null) { + // check if mapping is provided otherwise returned values will be treated as channel names + List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); + if (childElements != null && childElements.size() > 0) { + BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( + IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver"); + ManagedMap channelMap = new ManagedMap(); + for (Element childElement : childElements) { + channelMap.put(childElement.getAttribute("value"), + new RuntimeBeanReference(childElement.getAttribute("channel"))); + } + channelResolverBuilder.addPropertyValue("channelMap", channelMap); + beanDefinition.getPropertyValues().add("channelResolver", channelResolverBuilder.getBeanDefinition()); + } + } + return beanDefinition; + } + + protected abstract BeanDefinition doParseRouter(Element element, ParserContext parserContext); + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java index 45f6d9d181..d2a3d0f241 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java @@ -18,6 +18,7 @@ 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.xml.ParserContext; @@ -38,10 +39,11 @@ public abstract class AbstractRouterParser extends AbstractConsumerEndpointParse IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-send-failures"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-channel-name-resolution-failures"); - this.parseRouter(element, builder, parserContext); + BeanDefinition targetRouterBeanDefinition = this.parseRouter(element, parserContext); + builder.addPropertyValue("targetObject", targetRouterBeanDefinition); return builder; } - protected abstract void parseRouter(Element element, BeanDefinitionBuilder rootBuilder, ParserContext parserContext); + protected abstract BeanDefinition parseRouter(Element element, ParserContext parserContext); } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java index a88530a652..cfd7c32749 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java @@ -16,12 +16,18 @@ package org.springframework.integration.config.xml; +import java.util.List; + import org.w3c.dom.Element; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; /** * Parser for the <router/> element. @@ -30,6 +36,9 @@ import org.springframework.util.StringUtils; */ public class DefaultRouterParser extends AbstractDelegatingConsumerEndpointParser { + private static final String CHANNEL_RESOLVER_PROPERTY = "channelResolver"; + + @Override String getFactoryBeanClassName() { return IntegrationNamespaceUtils.BASE_PACKAGE + ".config.RouterFactoryBean"; @@ -43,13 +52,31 @@ public class DefaultRouterParser extends AbstractDelegatingConsumerEndpointParse @Override protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { String resolverBeanName = element.getAttribute("channel-resolver"); - if (!StringUtils.hasText(resolverBeanName)) { - BeanDefinitionBuilder resolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.BeanFactoryChannelResolver"); - resolverBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName( - resolverBuilder.getBeanDefinition(), parserContext.getRegistry()); + List mappingElements = DomUtils.getChildElementsByTagName(element, "mapping"); + if (!CollectionUtils.isEmpty(mappingElements)) { + if (StringUtils.hasText(resolverBeanName)) { + parserContext.getReaderContext().error( + "The 'channel-resolver' attribute and 'mapping' sub-elements are mutually exclusive.", + parserContext.extractSource(element)); + } + BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( + IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver"); + ManagedMap channelMap = new ManagedMap(); + for (Element mappingElement : mappingElements) { + channelMap.put(mappingElement.getAttribute("value"), + new RuntimeBeanReference(mappingElement.getAttribute("channel"))); + } + channelResolverBuilder.addPropertyValue("channelMap", channelMap); + builder.addPropertyValue(CHANNEL_RESOLVER_PROPERTY, channelResolverBuilder.getBeanDefinition()); + } + else if (StringUtils.hasText(resolverBeanName)) { + builder.addPropertyReference(CHANNEL_RESOLVER_PROPERTY, resolverBeanName); + } + else { + RootBeanDefinition resolverBeanDefintion = new RootBeanDefinition( + IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.BeanFactoryChannelResolver"); + builder.addPropertyValue(CHANNEL_RESOLVER_PROPERTY, resolverBeanDefintion); } - builder.addPropertyReference("channelResolver", resolverBeanName); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "default-output-channel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "resolution-required"); diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java index 342711de94..dee7eafcf2 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.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. @@ -16,16 +16,11 @@ package org.springframework.integration.config.xml; -import java.util.List; - import org.w3c.dom.Element; -import org.springframework.beans.factory.config.RuntimeBeanReference; +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.support.ManagedMap; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.xml.DomUtils; /** * Parser for the <header-value-router/> element. @@ -34,30 +29,14 @@ import org.springframework.util.xml.DomUtils; * @author Mark Fisher * @since 1.0.3 */ -public class HeaderValueRouterParser extends AbstractRouterParser { +public class HeaderValueRouterParser extends AbstractChannelNameResolvingRouterParser { @Override - @SuppressWarnings("unchecked") - protected void parseRouter(Element element, BeanDefinitionBuilder rootBuilder, ParserContext parserContext) { + protected BeanDefinition doParseRouter(Element element, ParserContext parserContext) { BeanDefinitionBuilder headerValueRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( IntegrationNamespaceUtils.BASE_PACKAGE + ".router.HeaderValueRouter"); headerValueRouterBuilder.addConstructorArgValue(element.getAttribute("header-name")); - // check if mapping is provided otherwise header values will be treated as channel names - List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); - if (childElements != null && childElements.size() > 0) { - BeanDefinitionBuilder mapBasedChannelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver"); - ManagedMap channelMap = new ManagedMap(); - for (Element childElement : childElements) { - channelMap.put(childElement.getAttribute("value"), - new RuntimeBeanReference(childElement.getAttribute("channel"))); - } - mapBasedChannelResolverBuilder.addPropertyValue("channelMap", channelMap); - String resolverBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName( - mapBasedChannelResolverBuilder.getBeanDefinition(), parserContext.getRegistry()); - headerValueRouterBuilder.addPropertyReference("channelResolver", resolverBeanName); - } - rootBuilder.addPropertyValue("targetObject", headerValueRouterBuilder.getBeanDefinition()); + return headerValueRouterBuilder.getBeanDefinition(); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java index e8347cc9d6..8bf6023e29 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java @@ -20,6 +20,7 @@ import java.util.List; import org.w3c.dom.Element; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedMap; @@ -39,7 +40,7 @@ public class PayloadTypeRouterParser extends AbstractRouterParser { @Override @SuppressWarnings("unchecked") - protected void parseRouter(Element element, BeanDefinitionBuilder rootBuilder, ParserContext parserContext) { + protected BeanDefinition parseRouter(Element element, ParserContext parserContext) { BeanDefinitionBuilder payloadTypeRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( IntegrationNamespaceUtils.BASE_PACKAGE + ".router.PayloadTypeRouter"); List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); @@ -56,7 +57,7 @@ public class PayloadTypeRouterParser extends AbstractRouterParser { channelMap.put(typeName, new RuntimeBeanReference(childElement.getAttribute("channel"))); } payloadTypeRouterBuilder.addPropertyValue("payloadTypeChannelMap", channelMap); - rootBuilder.addPropertyValue("targetObject", payloadTypeRouterBuilder.getBeanDefinition()); + return payloadTypeRouterBuilder.getBeanDefinition(); } } 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 1c06a3696e..6c20c92071 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 @@ -1578,23 +1578,6 @@ - - - - - - - - - - - - - - - - - + - + @@ -1726,6 +1709,23 @@ + + + + + + + + + + + + + + + + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml b/org.springframework.integration/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml new file mode 100644 index 0000000000..fcce0a6a52 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java new file mode 100644 index 0000000000..92b0244f18 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java @@ -0,0 +1,126 @@ +/* + * 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.router.config; + +import static org.junit.Assert.assertNotNull; +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.integration.channel.PollableChannel; +import org.springframework.integration.core.Message; +import org.springframework.integration.core.MessageChannel; +import org.springframework.integration.message.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class RouterWithMappingTests { + + @Autowired + private MessageChannel expressionRouter; + + @Autowired + private MessageChannel pojoRouter; + + @Autowired + private PollableChannel fooChannelForExpression; + + @Autowired + private PollableChannel barChannelForExpression; + + @Autowired + private PollableChannel defaultChannelForExpression; + + @Autowired + private PollableChannel fooChannelForPojo; + + @Autowired + private PollableChannel barChannelForPojo; + + @Autowired + private PollableChannel defaultChannelForPojo; + + + @Test + public void expressionRouter() { + Message message1 = MessageBuilder.withPayload(new TestBean("foo")).build(); + Message message2 = MessageBuilder.withPayload(new TestBean("bar")).build(); + Message message3 = MessageBuilder.withPayload(new TestBean("baz")).build(); + expressionRouter.send(message1); + assertNotNull(fooChannelForExpression.receive(0)); + assertNull(barChannelForExpression.receive(0)); + assertNull(defaultChannelForExpression.receive(0)); + expressionRouter.send(message2); + assertNotNull(barChannelForExpression.receive(0)); + assertNull(fooChannelForExpression.receive(0)); + assertNull(defaultChannelForExpression.receive(0)); + expressionRouter.send(message3); + assertNotNull(defaultChannelForExpression.receive(0)); + assertNull(fooChannelForExpression.receive(0)); + assertNull(barChannelForExpression.receive(0)); + } + + @Test + public void pojoRouter() { + Message message1 = MessageBuilder.withPayload(new TestBean("foo")).build(); + Message message2 = MessageBuilder.withPayload(new TestBean("bar")).build(); + Message message3 = MessageBuilder.withPayload(new TestBean("baz")).build(); + pojoRouter.send(message1); + assertNotNull(fooChannelForPojo.receive(0)); + assertNull(barChannelForPojo.receive(0)); + assertNull(defaultChannelForPojo.receive(0)); + pojoRouter.send(message2); + assertNotNull(barChannelForPojo.receive(0)); + assertNull(fooChannelForPojo.receive(0)); + assertNull(defaultChannelForPojo.receive(0)); + pojoRouter.send(message3); + assertNotNull(defaultChannelForPojo.receive(0)); + assertNull(fooChannelForPojo.receive(0)); + assertNull(barChannelForPojo.receive(0)); + } + + + private static class TestBean { + + private final String name; + + public TestBean(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } + + + @SuppressWarnings("unused") + private static class TestRouter { + + public String route(TestBean bean) { + return bean.getName(); + } + } + +}