diff --git a/docs/src/reference/docbook/router.xml b/docs/src/reference/docbook/router.xml index 87e46a78b9..e06166b650 100644 --- a/docs/src/reference/docbook/router.xml +++ b/docs/src/reference/docbook/router.xml @@ -1,8 +1,242 @@
- Router - + Routers +
+ Overview + + Routers are a crucial element in many messaging architectures. They consume + Messages from a Message Channel and forward each consumed message to one or + more different Message Channel depending on a set of conditions. + + + Spring Integration provides the following routers out-of-the-box: + + + + (Generic) Router + + + Header Value Router + + + XPath Router (Part of the XML Module) + + + Payload Type Router + + + Recipient List Router + + + Error Message Exception Type Router + + + + Router implementations share many configuration parameters. Yet, certain differences + exist between routers. Furthermore, the availability of configuration parameters + depends on whether Routers are used inside or outside of a chain. In order to + provide a quick overview, all available attributes are listed in the 2 tables + below. + + Routers Outside a Chain + + + + + + + + + + + Attribute + router + header value router + xpath router + payload type router + recipient list router + exception type router + + + + + apply-sequence + xxxxxxx + + + default-output-channel + xxxxxx + + + resolution-required + xxxxxx + + + ignore-send-failures + xxxxxx + + + timeout + xxxxxx + + + id + xxxxxx + + + auto-startup + xxxxxx + + + input-channel + xxxxxx + + + order + xxxxxx + + + channel-resolver + x + + + method + x + + + ref + x + + + expression + x + + + header-name + x + + + evaluate-as-string + x + + + xpath-expression-ref + x + + + +
+ Routers Inside a Chain + + + + + + + + + + + Attribute + router + header value router + xpath router + payload type router + recipient list router + exception type router + + + + + apply-sequence + xxxxxxx + + + default-output-channel + xxxxxx + + + resolution-required + xxxxxx + + + ignore-send-failures + xxxxxx + + + timeout + xxxxxx + + + id + + + + auto-startup + + + + input-channel + + + + order + + + + channel-resolver + x + + + method + x + + + ref + x + + + expression + x + + + header-name + x + + + evaluate-as-string + x + + + xpath-expression-ref + x + + + +
+ + Router parameters have been more standardized across all router implementations + in Spring Integration ??TBD??. Consequently, some parameters were removed or + added leaving the possibility of breaking older Spring Integration based applications. + + + Since Spring Integration ??TBD?? the "ignore-channel-name-resolution-failures" + attribute was renamed to "channel resolution-required". Also, "channel resolution-required" + defaults to "true". + + Prior to an attribute "resolution-required" existed that defaulted to false + causing messages to be dropped silently when no channel was resolved and + no "default-output-channel" was set. + + The new behavior will require at least one resolved channel and by default + will throw an Exception if no channel was determined (and the sent to it + was not successful). + + "resolution-required" is now removed. If you desire to drop messages silently + set "default-output-channel='nullChannel'". + +
Router Implementations diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java index 0ab0ddc0ad..4580252a1a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java @@ -45,8 +45,6 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean private volatile Boolean resolutionRequired; - private volatile Boolean ignoreChannelNameResolutionFailures; - private volatile Boolean applySequence; private volatile Boolean ignoreSendFailures; @@ -68,10 +66,6 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean this.resolutionRequired = resolutionRequired; } - public void setIgnoreChannelNameResolutionFailures(Boolean ignoreChannelNameResolutionFailures) { - this.ignoreChannelNameResolutionFailures = ignoreChannelNameResolutionFailures; - } - public void setApplySequence(Boolean applySequence) { this.applySequence = applySequence; } @@ -128,9 +122,6 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean if (this.timeout != null) { router.setTimeout(timeout.longValue()); } - if (this.ignoreChannelNameResolutionFailures != null) { - router.setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures); - } if (this.applySequence != null) { router.setApplySequence(this.applySequence); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java index 6f6079d288..d71772d157 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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,7 +44,6 @@ public abstract class AbstractRouterParser extends AbstractConsumerEndpointParse IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "resolution-required"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-send-failures"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-channel-name-resolution-failures"); BeanDefinition targetRouterBeanDefinition = this.parseRouter(element, parserContext); builder.addPropertyValue("targetObject", targetRouterBeanDefinition); return builder; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java index 49ec9c25fd..6d41dba418 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -78,7 +78,6 @@ public class DefaultRouterParser extends AbstractDelegatingConsumerEndpointParse IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "default-output-channel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "resolution-required"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-channel-name-resolution-failures"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-send-failures"); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java index 49b1ce36a9..a1e05df3b2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java @@ -30,6 +30,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessagingException; +import org.springframework.integration.channel.NullChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.integration.support.MessageBuilder; @@ -47,14 +48,13 @@ import org.springframework.util.StringUtils; * * @author Mark Fisher * @author Oleg Zhurakousky + * @author Gunnar Hillert */ @ManagedResource public abstract class AbstractMessageRouter extends AbstractMessageHandler { private volatile MessageChannel defaultOutputChannel; - private volatile boolean resolutionRequired; - private volatile boolean ignoreSendFailures; private volatile boolean applySequence; @@ -67,7 +67,7 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { private volatile ChannelResolver channelResolver; - private volatile boolean ignoreChannelNameResolutionFailures; + private volatile boolean resolutionRequired = true; protected volatile Map channelIdentifierMap = new ConcurrentHashMap(); @@ -120,9 +120,12 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { } /** - * Set the default channel where Messages should be sent if channel resolution fails to return any channels. If no - * default channel is provided, the router will either drop the Message or throw an Exception depending on the value - * of {@link #resolutionRequired}. + * Set the default channel where Messages should be sent if channel resolution + * fails to return any channels. If no default channel is provided and channel + * resolution fails to return any channels, the router will throw an + * {@link MessageDeliveryException}. + * + * If messages shall be ignored (dropped) instead, please provide a {@link NullChannel}. */ public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) { this.defaultOutputChannel = defaultOutputChannel; @@ -136,21 +139,12 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { this.messagingTemplate.setSendTimeout(timeout); } - /** - * Set whether this router should always be required to resolve at least one channel. The default is 'false'. To - * trigger an exception whenever the resolver returns null or an empty channel list, and this endpoint has no - * 'defaultOutputChannel' configured, set this value to 'true'. - */ - public void setResolutionRequired(boolean resolutionRequired) { - this.resolutionRequired = resolutionRequired; - } - /** * Specify whether this router should ignore any failure to resolve a channel name to * an actual MessageChannel instance when delegating to the ChannelResolver strategy. */ - public void setIgnoreChannelNameResolutionFailures(boolean ignoreChannelNameResolutionFailures) { - this.ignoreChannelNameResolutionFailures = ignoreChannelNameResolutionFailures; + public void setResolutionRequired(boolean resolutionRequired) { + this.resolutionRequired = resolutionRequired; } /** @@ -234,8 +228,7 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { if (!sent) { if (this.defaultOutputChannel != null) { this.messagingTemplate.send(this.defaultOutputChannel, message); - } - else if (this.resolutionRequired) { + } else { throw new MessageDeliveryException(message, "no channel resolved by router and no default output channel defined"); } @@ -260,12 +253,12 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { channel = this.channelResolver.resolveChannelName(channelName); } catch (ChannelResolutionException e) { - if (!this.ignoreChannelNameResolutionFailures) { + if (this.resolutionRequired) { throw new MessagingException(message, "failed to resolve channel name '" + channelName + "'", e); } } - if (channel == null && !this.ignoreChannelNameResolutionFailures) { + if (channel == null && this.resolutionRequired) { throw new MessagingException(message, "failed to resolve channel name '" + channelName + "'"); } diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd index b6ef2ab248..ae1be1d534 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd @@ -1097,31 +1097,32 @@ endpoint itself is a Polling Consumer for a channel with a queue. - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - + - - + + @@ -1986,337 +1987,658 @@ endpoint itself is a Polling Consumer for a channel with a queue. - - - - - - - - - - - - + - - - - - - - - - - - + + + + + - - - - - - - - - - - - + + + + + - - - - - - - - - - An expression to be evaluated to determine if this recipient should be included in the - recipient - list for a given input Message. The evaluation result of the expression must be a boolean. - If this - attribute is not defined, the channel will always be among the list of recipients. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - A value of the evaluation token that will be mapped to a channel reference - (e.g., mapping value='foo' channel='myChannel') - - - - - - - A reference to a bean that defines a Message Channel - (e.g., mapping value='foo' channel='myChannel') - - - - - - - - - - - - - - - Specify whether a failure to resolve a channel name returned by this - router should be ignored. - - - - - - + + + + + + + + + The "ref" attribute references the bean name of a custom + Router implementation that extends AbstractMessageRouter. + + Alternatively, the "ref" attribute may point to a + simple POJO that contains the @Router annotation. + + Instead of using the "ref" attribute you may also provide + the custom Router implementation as an inner bean + definition. + + However, keep in mind that using both the "ref" attribute + and an inner handler definition in the same Router + configuration is not allowed, as it creates an + ambiguous condition, and an Exception will be thrown. + + + + + + + + + + + + When implementing a custom Router using a plain POJO + the "ref" attribute may be combined with an explicit + method name using the "method" attribute. + + The referenced method may return either a MessageChannel + or a String type.Additionally, the method may return + either a single value or a collection. If a collection + is returned, the reply message will be sent to + multiple channels. + + Specifying a "method" attribute applies the same behavior + as when using the @Router annotation within POJOs. + + + + + + + + + + + + SpEL Expression to be evaulated at runtime. Allows you + to implement simple computations without implementing + a custom POJO router. Generally, the SpEL expression is + evaluated and the result is mapped to a channel using + "mapping" sub-elements. + + However, if no "mapping" sub-element is present, the + SpEL Expression will evaluate to a channel name directly. + + A SpEL expression may return also a Collection. Whenever + the expression returns multiple channel values the + Message will be forwarded to each channel. + + + + + + + + + + + + + + + + - - - - - - - - - - - Reference to the default channel where Messages should be sent if channel - resolution fails to - return any channels. If no default channel - is - provided, the router will either drop the Message or throw an - Exception - depending on the value of the "resolution-required" attribute. - - - - - - - - Specify whether this router should always be required to return at least - one channel or name. - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An expression to be evaluated to determine if this recipient + should be included in the recipient list for a given input + Message. The evaluation result of the expression must be a boolean. + If this attribute is not defined, the channel will always be + among the list of recipients. + + + + + + + + + + + + + + + + + An expression to be evaluated to determine if this recipient + should be included in the recipient list for a given input + Message. The evaluation result of the expression must be a boolean. + If this attribute is not defined, the channel will always be + among the list of recipients. + + + + + + + + + + + + + + + + + + + + + + + + + ). + ]]> + + + + + + + + + + + + + + + + ) + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + ) + + The most specific matching exception type is determined + by navigating the hierarchy of 'exception causes' + (e.g., payload.getCause()). + ]]> + + + + + + + + + + + + + + + + ) + + The most specific matching exception type is determined + by navigating the hierarchy of 'exception causes' + (e.g., payload.getCause()). + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A value of the evaluation token that will be mapped to a channel reference + (e.g., mapping value='foo' channel='myChannel') + + + + + + + A reference to a bean that defines a Message Channel + (e.g., mapping value='foo' channel='myChannel') + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An expression to be evaluated to determine if this recipient + should be included in the recipient list for a given input + Message. The evaluation result of the expression must be a boolean. + If this attribute is not defined, the channel will always be + among the list of recipients. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lifecycle attribute signaling if this component should be + started during Application Context startup. Defaults to true. + + + + + + + + + + + + + + + The receiving Message channel of this endpoint + + + + + + + + + + + + + + + + + Reference to the channel where Messages should be sent if + channel resolution fails to return any channels. If no + default output channel is provided, the router will throw an + Exception. + + If you would like to silently drop those messages instead, + add the "nullChannel" as the default output channel attribute + value. + + + + + + + + - Specify the maximum amount of time in milliseconds to wait when sending - Messages - to the target - MessageChannels. By default the send will block - indefinitely. + Specify the maximum amount of time in milliseconds to wait + when sending Messages to the target MessageChannels. By + default the send will block indefinitely. - + - Specify whether a failure to send to a single channel should be ignored. - Otherwise - MessageDeliveryExceptions will be thrown. + Specify whether a failure to send to a single channel should + be ignored. Otherwise MessageDeliveryExceptions will be thrown. + Defaults to false. + + + - + Specify whether sequence number and size headers should be added to each - Message. + Message. Defaults to false. + + + + + + + Specify whether channel names must always be successfully resolved + to existing channel instances. + + If set to 'true', a MessagingException will be raised in case + the channel cannot be resolved. Setting this attribute to 'false', + will cause any unresovable channels to be ignored. + + If not explicitly set, 'resolution-required' will + default to 'true'. + + + + + + @@ -2822,7 +3144,7 @@ Name of the header whose value will be used to route messages [OPTIONAL] Specifies the order in which this interceptor will be added to the existing channel interceptors (if any). - Negative value (e.g., -2) will signify BEFORE existing iinterceptors (if any). Positive + Negative value (e.g., -2) will signify BEFORE existing interceptors (if any). Positive value (e.g., 2) will signify AFTER existing interceptors (if any) diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/MethodInvokingRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/MethodInvokingRouterTests.java index dce1cca0bb..9dc5f16efa 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/MethodInvokingRouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/MethodInvokingRouterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -19,6 +19,7 @@ package org.springframework.integration.router; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import java.lang.reflect.Method; import java.util.ArrayList; @@ -28,6 +29,7 @@ import org.junit.Test; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessagingException; import org.springframework.integration.annotation.Header; import org.springframework.integration.channel.QueueChannel; @@ -134,7 +136,15 @@ public class MethodInvokingRouterTests { Message result2 = barChannel.receive(0); assertNotNull(result2); assertEquals("bar", result2.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } + + } @Test @@ -171,7 +181,14 @@ public class MethodInvokingRouterTests { Message result2 = barChannel.receive(0); assertNotNull(result2); assertEquals("bar", result2.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } + } @Test @@ -208,7 +225,14 @@ public class MethodInvokingRouterTests { Message result2 = barChannel.receive(0); assertNotNull(result2); assertEquals("bar", result2.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } + } @Test @@ -251,7 +275,13 @@ public class MethodInvokingRouterTests { assertEquals("bar", result2a.getPayload()); assertNotNull(result2b); assertEquals("bar", result2b.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } } @Test @@ -294,7 +324,13 @@ public class MethodInvokingRouterTests { Message result2b = barChannel.receive(0); assertNotNull(result2b); assertEquals("bar", result2b.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } } @Test @@ -337,7 +373,13 @@ public class MethodInvokingRouterTests { Message result2b = barChannel.receive(0); assertNotNull(result2b); assertEquals("bar", result2b.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } } @Test @@ -380,7 +422,14 @@ public class MethodInvokingRouterTests { assertEquals("bar", result2a.getPayload()); assertNotNull(result2b); assertEquals("bar", result2b.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } + } @Test @@ -423,7 +472,14 @@ public class MethodInvokingRouterTests { assertEquals("bar", result2a.getPayload()); assertNotNull(result2b); assertEquals("bar", result2b.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } + } @Test @@ -466,7 +522,14 @@ public class MethodInvokingRouterTests { assertEquals("bar", result2a.getPayload()); assertNotNull(result2b); assertEquals("bar", result2b.getPayload()); - router.handleMessage(badMessage); + + try { + router.handleMessage(badMessage); + fail(); + } catch (MessageDeliveryException e) { + /* Success */ + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java index acda1d9679..0a132ea2ee 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java @@ -67,14 +67,15 @@ public class PayloadTypeRouterTests { router.setChannelMapping(String.class.getName(), "newChannel"); assertEquals(1, router.getChannelIdentifiers(message1).size()); assertEquals("newChannel", router.getChannelIdentifiers(message1).iterator().next()); + + // validate exception is thrown if mappings were removed and + // channelResolutionRequires = true (which is the default) - // validate nothing happens if mappings were removed and resolutionRequires = false router.removeChannelMapping(String.class.getName()); router.removeChannelMapping(Integer.class.getName()); - router.handleMessage(message1); - - // validate exception is thrown if mappings were removed and resolutionRequires = true + router.setResolutionRequired(true); + try { router.handleMessage(message1); fail(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java index 6cfa951012..57bb3bd919 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -27,6 +27,7 @@ import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.GenericApplicationContext; import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessagingException; import org.springframework.integration.channel.QueueChannel; @@ -37,11 +38,12 @@ import org.springframework.util.CollectionUtils; /** * @author Mark Fisher * @author Oleg Zhurakousky + * @author Gunnar Hillert */ public class RouterTests { - @Test - public void nullChannelIgnoredByDefault() { + @Test(expected = MessageDeliveryException.class) + public void nullChannelRaisesMessageDeliveryExceptionByDefault() { AbstractMessageRouter router = new AbstractMessageRouter() { @Override protected List getChannelIdentifiers(Message message) { @@ -53,45 +55,7 @@ public class RouterTests { } @Test(expected = MessageDeliveryException.class) - public void nullChannelThrowsExceptionWhenResolutionRequired() { - AbstractMessageRouter router = new AbstractMessageRouter() { - @Override - protected List getChannelIdentifiers(Message message) { - return null; - } - }; - router.setResolutionRequired(true); - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - - @Test - public void emptyChannelListIgnoredByDefault() { - AbstractMessageRouter router = new AbstractMessageRouter() { - @Override - protected List getChannelIdentifiers(Message message) { - return null; - } - }; - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - - @Test(expected = MessageDeliveryException.class) - public void emptyChannelListThrowsExceptionWhenResolutionRequired() { - AbstractMessageRouter router = new AbstractMessageRouter() { - @Override - protected List getChannelIdentifiers(Message message) { - return null; - } - }; - router.setResolutionRequired(true); - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - - @Test - public void nullChannelIdentifierIgnoredByDefault() { + public void nullChannelIdentifierUsingChannelResolverRaisesMessageDeliveryExceptionByDefault() { AbstractMessageRouter router = new AbstractMessageRouter() { @Override protected List getChannelIdentifiers(Message message) { @@ -104,8 +68,8 @@ public class RouterTests { router.handleMessage(message); } - @Test - public void nullChannelIdentifierInListIgnoredByDefault() { + @Test(expected = MessageDeliveryException.class) + public void nullChannelIdentifierInListRaisesMessageDeliveryExceptionByDefault() { AbstractMessageRouter router = new AbstractMessageRouter() { @Override protected List getChannelIdentifiers(Message message) { @@ -119,35 +83,7 @@ public class RouterTests { } @Test(expected = MessageDeliveryException.class) - public void nullChannelIdentifierTriggersExceptionWhenResolutionRequired() { - AbstractMessageRouter router = new AbstractMessageRouter() { - protected List getChannelIdentifiers(Message message) { - return null; - } - }; - TestChannelResolver channelResolver = new TestChannelResolver(); - router.setChannelResolver(channelResolver); - router.setResolutionRequired(true); - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - - @Test(expected = MessageDeliveryException.class) - public void nullChannelIdentifierInListTriggersExceptionWhenResolutionRequired() { - AbstractMessageRouter router = new AbstractMessageRouter() { - protected List getChannelIdentifiers(Message message) { - return Collections.singletonList(null); - } - }; - TestChannelResolver channelResolver = new TestChannelResolver(); - router.setChannelResolver(channelResolver); - router.setResolutionRequired(true); - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - - @Test - public void emptyChannelNameArrayIgnoredByDefault() { + public void emptyChannelNameArrayRaisesMessageDeliveryExceptionByDefault() { AbstractMessageRouter router = new AbstractMessageRouter() { protected List getChannelIdentifiers(Message message) { return new ArrayList(); @@ -159,21 +95,6 @@ public class RouterTests { router.handleMessage(message); } - @Test(expected = MessageDeliveryException.class) - public void emptyChannelNameArrayThrowsExceptionWhenResolutionRequired() { - AbstractMessageRouter router = new AbstractMessageRouter() { - @SuppressWarnings("unchecked") - protected List getChannelIdentifiers(Message message) { - return CollectionUtils.arrayToList(new String[] {}); - } - }; - TestChannelResolver channelResolver = new TestChannelResolver(); - router.setChannelResolver(channelResolver); - router.setResolutionRequired(true); - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - @Test(expected = MessagingException.class) public void channelMappingIsRequiredWhenResolvingChannelNames() { AbstractMessageRouter router = new AbstractMessageRouter() { @@ -203,4 +124,282 @@ public class RouterTests { assertEquals("test", reply.getPayload()); } + @Test + public void beanFactoryWithRouterAndMultipleCommaSeparatedChannelNames() { + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "testChannel1,testChannel2" }); + } + }; + + QueueChannel testChannel1 = new QueueChannel(); + QueueChannel testChannel2 = new QueueChannel(); + + GenericApplicationContext context = new GenericApplicationContext(); + + context.getBeanFactory().registerSingleton("testChannel1", testChannel1); + context.getBeanFactory().registerSingleton("testChannel2", testChannel2); + + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + Message reply1 = testChannel1.receive(0); + assertEquals("test", reply1.getPayload()); + + Message reply2 = testChannel2.receive(0); + assertEquals("test", reply2.getPayload()); + } + + @Test(expected = MessagingException.class) + public void channelResolutionIsRequiredByDefault() { + + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "testChannelDoesNotExist", "testChannel" }); + } + }; + + QueueChannel testChannel = new QueueChannel(); + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("testChannel", testChannel); + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + } + + @Test + public void unresolvableChannelIdentifierInListAreIgnoredWhenResolutionRequiredIsFalse() { + + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "testChannelDoesNotExist", "testChannel" }); + } + }; + + router.setResolutionRequired(false); + + QueueChannel testChannel = new QueueChannel(); + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("testChannel", testChannel); + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + Message reply = testChannel.receive(0); + assertEquals("test", reply.getPayload()); + + } + + @Test + public void beanFactoryWithRouterAndChannelPrefix() { + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "MyChannel" }); + } + }; + + router.setPrefix("testing_"); + + QueueChannel testChannel = new QueueChannel(); + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("testing_MyChannel", testChannel); + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + Message reply = testChannel.receive(0); + assertEquals("test", reply.getPayload()); + } + + + @Test(expected = MessagingException.class) + public void beanFactoryWithRouterAndChannelPrefixFailing() { + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "testing_MyChannel" }); + } + }; + + router.setPrefix("testing_"); + + QueueChannel testChannel = new QueueChannel(); + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("testing_MyChannel", testChannel); + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + } + + @Test + public void beanFactoryWithRouterAndChannelSuffix() { + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "MyChannel" }); + } + }; + + router.setSuffix("_withSuffix"); + + QueueChannel testChannel = new QueueChannel(); + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("MyChannel_withSuffix", testChannel); + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + Message reply = testChannel.receive(0); + assertEquals("test", reply.getPayload()); + } + + @Test(expected = MessagingException.class) + public void beanFactoryWithRouterAndChannelSuffixFailing() { + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new String[] { "MyChannel_withSuffix" }); + } + }; + + router.setSuffix("_withSuffix"); + + QueueChannel testChannel = new QueueChannel(); + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("MyChannel_withSuffix", testChannel); + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + } + + @Test + public void beanFactoryWithRouterAndChannelIdentifiersInListWithinAList() { + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + + List channelNames1 = CollectionUtils.arrayToList(new String[] { "channel1" }); + List channelNames2 = CollectionUtils.arrayToList(new String[] { "channel2" }); + + List listWithListOfChannelNames = new ArrayList(); + + listWithListOfChannelNames.add(channelNames1); + listWithListOfChannelNames.add(channelNames2); + + return listWithListOfChannelNames; + } + }; + + QueueChannel testChannel1 = new QueueChannel(); + QueueChannel testChannel2 = new QueueChannel(); + + GenericApplicationContext context = new GenericApplicationContext(); + + context.getBeanFactory().registerSingleton("channel1", testChannel1); + context.getBeanFactory().registerSingleton("channel2", testChannel2); + + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + Message reply1 = testChannel1.receive(0); + assertEquals("test", reply1.getPayload()); + + Message reply2 = testChannel2.receive(0); + assertEquals("test", reply2.getPayload()); + } + + @Test + public void beanFactoryWithRouterAndChannelIdentifiersInMessageChannelArrayWithinAList() { + + final QueueChannel testChannel1 = new QueueChannel(); + final QueueChannel testChannel2 = new QueueChannel(); + + AbstractMessageRouter router = new AbstractMessageRouter() { + + protected List getChannelIdentifiers(Message message) { + + MessageChannel[] channelNames1 = new MessageChannel[] { testChannel1 }; + MessageChannel[] channelNames2 = new MessageChannel[] { testChannel2 }; + + List listWithListOfChannelNames = new ArrayList(); + + listWithListOfChannelNames.add(channelNames1); + listWithListOfChannelNames.add(channelNames2); + + return listWithListOfChannelNames; + } + }; + + GenericApplicationContext context = new GenericApplicationContext(); + + context.getBeanFactory().registerSingleton("channel1", testChannel1); + context.getBeanFactory().registerSingleton("channel2", testChannel2); + + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + Message reply1 = testChannel1.receive(0); + assertEquals("test", reply1.getPayload()); + + Message reply2 = testChannel2.receive(0); + assertEquals("test", reply2.getPayload()); + } + + @Test + public void beanFactoryWithRouterAndRetrieveChannelIdentifiersUsingDefaultConversionService() { + + final QueueChannel testChannel1 = new QueueChannel(); + final QueueChannel testChannel2 = new QueueChannel(); + + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new Integer[] { 100, 200 }); + } + }; + + GenericApplicationContext context = new GenericApplicationContext(); + + context.getBeanFactory().registerSingleton("100", testChannel1); + context.getBeanFactory().registerSingleton("200", testChannel2); + + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + Message reply1 = testChannel1.receive(0); + assertEquals("test", reply1.getPayload()); + + Message reply2 = testChannel2.receive(0); + assertEquals("test", reply2.getPayload()); + } + + private class CustomObjectWithChannelName { + + String channel = "channel1"; + + public String getChannel() { + return this.channel; + } + + } + + @Test(expected = MessagingException.class) + public void beanFactoryWithRouterAndRetrieveChannelIdentifierUsingDefaultConversionServiceFailing() { + + final QueueChannel testChannel1 = new QueueChannel(); + + AbstractMessageRouter router = new AbstractMessageRouter() { + @SuppressWarnings("unchecked") + protected List getChannelIdentifiers(Message message) { + return CollectionUtils.arrayToList(new CustomObjectWithChannelName[] { new CustomObjectWithChannelName() }); + } + }; + + GenericApplicationContext context = new GenericApplicationContext(); + + context.getBeanFactory().registerSingleton("channel1", testChannel1); + + router.setBeanFactory(context); + router.handleMessage(new GenericMessage("test")); + + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/ErrorChannelRoutingTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/router/config/ErrorChannelRoutingTests-context.xml index 0315cc55af..588fb508fe 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/ErrorChannelRoutingTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/ErrorChannelRoutingTests-context.xml @@ -3,10 +3,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd - http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd"> + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"> - + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java index 9bb53576db..372d5cc70b 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -49,6 +49,7 @@ import org.springframework.integration.test.util.TestUtils; /** * @author Mark Fisher * @author Jonas Partner + * @author Gunnar Hillert */ public class RouterParserTests { @@ -121,12 +122,11 @@ public class RouterParserTests { input.send(new GenericMessage(3)); } - @Test - public void testIgnoreChannelNameResolutionFailures() { + public void testResolutionRequiredIsTrue() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "routerParserTests.xml", this.getClass()); context.start(); - MessageChannel input = (MessageChannel) context.getBean("ignoreChannelNameResolutionFailuresInput"); + MessageChannel input = (MessageChannel) context.getBean("resolutionRequiredIsTrueInput"); input.send(new GenericMessage("channelThatDoesNotExist")); } @@ -177,7 +177,7 @@ public class RouterParserTests { assertEquals(new Integer(3), message3.getHeaders().getSequenceNumber()); assertEquals(new Integer(3), message3.getHeaders().getSequenceSize()); } - + @Test public void testErrorChannel(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml index ebb93cb347..7321a6d514 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml @@ -21,7 +21,7 @@ + resolution-required="false"> @@ -40,7 +40,7 @@ + resolution-required="false"> diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java index 0d6fb5bd6e..ea3baaeccb 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -69,7 +69,6 @@ public class RouterWithMappingTests { @Autowired private PollableChannel defaultChannelForPojo; - @Test public void expressionRouter() { Message message1 = MessageBuilder.withPayload(new TestBean("foo")).build(); @@ -115,7 +114,6 @@ public class RouterWithMappingTests { assertNull(barChannelForPojo.receive(0)); } - private static class TestBean { private final String name; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/routerParserTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/router/config/routerParserTests.xml index 869fef041b..3a1c69ac26 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/routerParserTests.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/routerParserTests.xml @@ -73,9 +73,9 @@ - + - + diff --git a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.1.xsd b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.1.xsd index 4e3f00ccd9..35f85d7553 100644 --- a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.1.xsd +++ b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.1.xsd @@ -342,98 +342,109 @@ - - - - - Defines an XPath router. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Specify whether this router should always be required to return at least one channel or name. - - - - - - - Specify whether a failure to resolve a channel name returned by this router should be ignored. - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + Reference to the XPathExpression instance to be + evaluated against the input Message's payload. Either + this or 'xpath-expression' must be provided, but not + both. + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + @@ -468,12 +479,26 @@ + + + representing channel names. Typically such list + will contain a single channel name. However, based + on the result of an XPath Expression the XPath router + can also take on the characteristics of the + Recipient List Router if the XPath Expression + returns more then one value, thus resulting in + the List containing more then one channel + name. + + In that case Message will be sent to all channels + in the list. + ]]> + - - - Defines an XPath expression. - - diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java index 768d9f1688..69b62d47d6 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java @@ -125,19 +125,6 @@ public class XPathRouterParserTests { inputChannel.send(docMessage); assertEquals("Wrong number of messages", 1, outputChannel.getQueueSize()); } - - @Test - public void testSetChannelResolver() throws Exception { - StringBuffer contextBuffer = new StringBuffer(""); - contextBuffer.append(""); - EventDrivenConsumer consumer = buildContext(contextBuffer.toString()); - - DirectFieldAccessor accessor = new DirectFieldAccessor(consumer); - Object handler = accessor.getPropertyValue("handler"); - accessor = new DirectFieldAccessor(handler); - Object resolver = accessor.getPropertyValue("channelResolver"); - assertEquals("Wrong channel resolver ",StubChannelResolver.class, resolver.getClass()); - } @Test public void testSetResolutionRequiredFalse() throws Exception { @@ -163,32 +150,6 @@ public class XPathRouterParserTests { assertEquals("Resolution required not set to true ", true, resolutionRequired); } - @Test - public void testIgnoreChannelNameResolutionFailuresFalse() throws Exception { - StringBuffer contextBuffer = new StringBuffer( - ""); - EventDrivenConsumer consumer = buildContext(contextBuffer.toString()); - - DirectFieldAccessor accessor = new DirectFieldAccessor(consumer); - Object handler = accessor.getPropertyValue("handler"); - accessor = new DirectFieldAccessor(handler); - Object ignoreChannelNameResolutionFailures = accessor.getPropertyValue("ignoreChannelNameResolutionFailures"); - assertEquals("ignoreChannelNameResolutionFailures not set to false", false, ignoreChannelNameResolutionFailures); - } - - @Test - public void testIgnoreChannelNameResolutionFailuresTrue() throws Exception { - StringBuffer contextBuffer = new StringBuffer( - ""); - EventDrivenConsumer consumer = buildContext(contextBuffer.toString()); - - DirectFieldAccessor accessor = new DirectFieldAccessor(consumer); - Object handler = accessor.getPropertyValue("handler"); - accessor = new DirectFieldAccessor(handler); - Object ignoreChannelNameResolutionFailures = accessor.getPropertyValue("ignoreChannelNameResolutionFailures"); - assertEquals("ignoreChannelNameResolutionFailures not set to true ", true, ignoreChannelNameResolutionFailures); - } - @Test public void testSetDefaultOutputChannel() throws Exception { StringBuffer contextBuffer = new StringBuffer("");