INT-1136 the 'router' element now supports 'mapping' sub-elements (mapping the returned 'value' to a 'channel' reference)

This commit is contained in:
Mark Fisher
2010-05-08 23:29:57 +00:00
parent 58c5138ec0
commit bd5fadf6d0
9 changed files with 305 additions and 59 deletions

View File

@@ -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);
}

View File

@@ -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<Element> childElements = DomUtils.getChildElementsByTagName(element, "mapping");
if (childElements != null && childElements.size() > 0) {
BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver");
ManagedMap<String, RuntimeBeanReference> channelMap = new ManagedMap<String, RuntimeBeanReference>();
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);
}

View File

@@ -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);
}

View File

@@ -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 &lt;router/&gt; 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<Element> 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<String, RuntimeBeanReference> channelMap = new ManagedMap<String, RuntimeBeanReference>();
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");

View File

@@ -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 &lt;header-value-router/&gt; 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<Element> 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();
}
}

View File

@@ -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<Element> 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();
}
}

View File

@@ -1578,23 +1578,6 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="channelNameResolvingRouterType">
<xsd:sequence>
<xsd:element name="mapping" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="value" type="xsd:string" />
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="header-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -1679,11 +1662,11 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="channelNameResolvingRouterType">
<xsd:all>
<xsd:sequence>
<xsd:element name="poller" type="innerPollerType"
minOccurs="0" maxOccurs="1" />
<xsd:element ref="beans:bean" minOccurs="0" maxOccurs="1" />
</xsd:all>
</xsd:sequence>
<xsd:attribute name="ref" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
@@ -1726,6 +1709,23 @@
<xsd:complexType name="channelNameResolvingRouterType">
<xsd:complexContent>
<xsd:extension base="channelResolvingRouterType">
<xsd:sequence>
<xsd:element name="mapping" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="value" type="xsd:string" />
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="ignore-channel-name-resolution-failures"
type="xsd:string">
<xsd:annotation>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<channel id="fooChannelForExpression">
<queue capacity="1" />
</channel>
<channel id="barChannelForExpression">
<queue capacity="1" />
</channel>
<channel id="defaultChannelForExpression">
<queue capacity="1" />
</channel>
<router input-channel="expressionRouter" expression="payload.name"
default-output-channel="defaultChannelForExpression"
ignore-channel-name-resolution-failures="true">
<mapping value="foo" channel="fooChannelForExpression"/>
<mapping value="bar" channel="barChannelForExpression"/>
</router>
<channel id="fooChannelForPojo">
<queue capacity="1" />
</channel>
<channel id="barChannelForPojo">
<queue capacity="1" />
</channel>
<channel id="defaultChannelForPojo">
<queue capacity="1" />
</channel>
<router input-channel="pojoRouter" ref="testBean"
default-output-channel="defaultChannelForPojo"
ignore-channel-name-resolution-failures="true">
<mapping value="foo" channel="fooChannelForPojo"/>
<mapping value="bar" channel="barChannelForPojo"/>
</router>
<beans:bean id="testBean" class="org.springframework.integration.router.config.RouterWithMappingTests$TestRouter"/>
</beans:beans>

View File

@@ -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();
}
}
}