Refactored router hierarchy by adding an AbstractMessageRouter base class and removing the ChannelResolver delegation. The routers that return channel names now accept a ChannelMapping strategy. The BeanNameChannelMapping is typically used as a default. The mapping routers also support "prefix" and "suffix" properties.

This commit is contained in:
Mark Fisher
2008-10-10 23:47:22 +00:00
parent ac85cb1ecf
commit cb90d39657
33 changed files with 910 additions and 822 deletions

View File

@@ -22,8 +22,7 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.MessageConsumer;
import org.springframework.integration.router.MethodInvokingChannelResolver;
import org.springframework.integration.router.RouterEndpoint;
import org.springframework.integration.router.MethodInvokingRouter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -41,8 +40,7 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
@Override
protected MessageConsumer createConsumer(Object bean, Method method, Router annotation) {
MethodInvokingChannelResolver resolver = new MethodInvokingChannelResolver(bean, method);
RouterEndpoint router = new RouterEndpoint(resolver);
MethodInvokingRouter router = new MethodInvokingRouter(bean, method);
String defaultOutputChannelName = annotation.defaultOutputChannel();
if (StringUtils.hasText(defaultOutputChannelName)) {
MessageChannel defaultOutputChannel = this.channelRegistry.lookupChannel(defaultOutputChannelName);

View File

@@ -19,9 +19,12 @@ package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.router.MethodInvokingChannelResolver;
import org.springframework.integration.router.RouterEndpoint;
import org.springframework.integration.router.BeanNameChannelMapping;
import org.springframework.integration.router.MethodInvokingRouter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Parser for the <router/> element.
@@ -32,10 +35,19 @@ public class RouterParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseConsumer(Element element, ParserContext parserContext) {
String adapterBeanName = this.parseAdapter(element, parserContext, MethodInvokingChannelResolver.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RouterEndpoint.class);
builder.addConstructorArgReference(adapterBeanName);
builder.addPropertyReference("channelRegistry", MessageBusParser.MESSAGE_BUS_BEAN_NAME);
String ref = element.getAttribute(REF_ATTRIBUTE);
Assert.hasText(ref, "The '" + REF_ATTRIBUTE + "' attribute is required.");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingRouter.class);
builder.addConstructorArgReference(ref);
if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) {
String method = element.getAttribute(METHOD_ATTRIBUTE);
builder.addConstructorArgValue(method);
}
BeanDefinitionBuilder channelMappingBuilder =
BeanDefinitionBuilder.genericBeanDefinition(BeanNameChannelMapping.class);
String channelMappingBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
channelMappingBuilder.getBeanDefinition(), parserContext.getRegistry());
builder.addPropertyReference("channelMapping", channelMappingBeanName);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "default-output-channel");
return builder;
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2008 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;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessagingException;
import org.springframework.util.Assert;
/**
* A base class for router implementations that return only
* the channel name(s) rather than {@link MessageChannel} instances.
*
* @author Mark Fisher
*/
public abstract class AbstractChannelMappingMessageRouter extends AbstractMessageRouter implements BeanFactoryAware, InitializingBean {
private volatile ChannelMapping channelMapping;
private volatile String prefix;
private volatile String suffix;
private volatile BeanFactory beanFactory;
public void setChannelMapping(ChannelMapping channelMapping) {
this.channelMapping = channelMapping;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void afterPropertiesSet() {
if (this.channelMapping == null) {
Assert.notNull(beanFactory, "either a ChannelMapping or BeanFactory is required");
this.channelMapping = new BeanNameChannelMapping(this.beanFactory);
}
}
@Override
protected final Collection<MessageChannel> resolveChannels(Message<?> message) {
this.afterPropertiesSet();
Collection<MessageChannel> channels = new ArrayList<MessageChannel>();
String[] channelNames = this.resolveChannelNames(message);
if (channelNames == null) {
return null;
}
for (String channelName : channelNames) {
if (channelName != null) {
Assert.state(this.channelMapping != null,
"unable to resolve channels, no ChannelMapping available");
if (this.prefix != null) {
channelName = this.prefix + channelName;
}
if (this.suffix != null) {
channelName = channelName + suffix;
}
MessageChannel channel = this.channelMapping.getChannel(channelName);
if (channel == null) {
throw new MessagingException(message,
"unable to resolve channel '" + channelName + "'");
}
channels.add(channel);
}
}
return channels;
}
/**
* Subclasses must implement this method to return the channel name(s).
*/
protected abstract String[] resolveChannelNames(Message<?> message);
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2002-2008 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;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessagingException;
import org.springframework.util.Assert;
/**
* A base class for {@link ChannelResolver} implementations that return only
* the channel name(s) rather than {@link MessageChannel} instances.
*
* @author Mark Fisher
*/
public abstract class AbstractChannelNameResolver implements ChannelResolver, ChannelRegistryAware {
private ChannelRegistry channelRegistry;
public void setChannelRegistry(ChannelRegistry channelRegistry) {
this.channelRegistry = channelRegistry;
}
public final Collection<MessageChannel> resolveChannels(Message<?> message) {
Collection<MessageChannel> channels = new ArrayList<MessageChannel>();
String[] channelNames = this.resolveChannelNames(message);
if (channelNames == null) {
return null;
}
for (String channelName : channelNames) {
if (channelName != null) {
Assert.state(this.channelRegistry != null,
"unable to resolve channels, no ChannelRegistry available");
MessageChannel channel = this.channelRegistry.lookupChannel(channelName);
if (channel == null) {
throw new MessagingException(message,
"unable to resolve chnanel '" + channelName + "'");
}
channels.add(channel);
}
}
return channels;
}
/**
* Subclasses must implement this method to return the channel name(s).
*/
protected abstract String[] resolveChannelNames(Message<?> message);
}

View File

@@ -18,21 +18,18 @@ package org.springframework.integration.router;
import java.util.Collection;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.endpoint.AbstractMessageConsumer;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.util.Assert;
/**
* Base class for Message Routers.
*
* @author Mark Fisher
*/
public class RouterEndpoint extends AbstractMessageConsumer implements ChannelRegistryAware {
private final ChannelResolver channelResolver;
public abstract class AbstractMessageRouter extends AbstractMessageConsumer {
private volatile MessageChannel defaultOutputChannel;
@@ -41,18 +38,12 @@ public class RouterEndpoint extends AbstractMessageConsumer implements ChannelRe
private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
public RouterEndpoint(ChannelResolver channelResolver) {
Assert.notNull(channelResolver, "ChannelResolver must not be null");
this.channelResolver = channelResolver;
}
public void setChannelRegistry(ChannelRegistry channelRegistry) {
if (this.channelResolver instanceof ChannelRegistryAware) {
((ChannelRegistryAware) this.channelResolver).setChannelRegistry(channelRegistry);
}
}
/**
* 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}.
*/
public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) {
this.defaultOutputChannel = defaultOutputChannel;
}
@@ -78,7 +69,7 @@ public class RouterEndpoint extends AbstractMessageConsumer implements ChannelRe
@Override
protected void onMessageInternal(Message<?> message) {
boolean sent = false;
Collection<MessageChannel> results = this.channelResolver.resolveChannels(message);
Collection<MessageChannel> results = this.resolveChannels(message);
if (results != null) {
for (MessageChannel channel : results) {
if (channel != null) {
@@ -99,4 +90,10 @@ public class RouterEndpoint extends AbstractMessageConsumer implements ChannelRe
}
}
/**
* Subclasses must implement this method to return the target channels for
* a given Message.
*/
protected abstract Collection<MessageChannel> resolveChannels(Message<?> message);
}

View File

@@ -21,9 +21,10 @@ import org.springframework.integration.message.Message;
/**
* @author Mark Fisher
*/
public abstract class AbstractSingleChannelNameResolver extends AbstractChannelNameResolver {
public abstract class AbstractSingleChannelNameRouter extends AbstractChannelMappingMessageRouter {
public final String[] resolveChannelNames(Message<?> message) {
@Override
protected final String[] resolveChannelNames(Message<?> message) {
String channelName = this.resolveChannelName(message);
return (channelName != null) ? new String[] { channelName } : null;
}

View File

@@ -25,12 +25,12 @@ import org.springframework.integration.message.Message;
/**
* @author Mark Fisher
*/
public abstract class AbstractSingleChannelResolver implements ChannelResolver {
public abstract class AbstractSingleChannelRouter extends AbstractMessageRouter {
public Collection<MessageChannel> resolveChannels(Message<?> message) {
@Override
protected final Collection<MessageChannel> resolveChannels(Message<?> message) {
MessageChannel channel = this.resolveChannel(message);
return (channel != null) ?
Collections.singletonList(channel) : null;
return (channel != null) ? Collections.singletonList(channel) : null;
}
protected abstract MessageChannel resolveChannel(Message<?> message);

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2008 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;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.util.Assert;
/**
* An implementation of the ChannelMapping strategy that retrieves a
* MessageChannel instance from the {@link BeanFactory} using the provided
* name.
*
* @author Mark Fisher
*/
public class BeanNameChannelMapping implements ChannelMapping, BeanFactoryAware {
private volatile BeanFactory beanFactory;
/**
* Constructor for use within a context where the BeanFactory will be
* injected via the {@link #setBeanFactory(BeanFactory)} callback method.
*/
public BeanNameChannelMapping() {
}
/**
* Constructor for programmatic creation from within other components that
* already have access to the {@link BeanFactory}.
*/
public BeanNameChannelMapping(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public MessageChannel getChannel(String name) {
Assert.state(this.beanFactory != null, "beanFactory must not be null");
return (MessageChannel) this.beanFactory.getBean(name, MessageChannel.class);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2002-2008 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;
import org.springframework.integration.channel.MessageChannel;
/**
* Strategy for mapping from a name to a {@link MessageChannel}.
*
* @author Mark Fisher
*/
public interface ChannelMapping {
MessageChannel getChannel(String name);
}

View File

@@ -23,8 +23,6 @@ import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageMappingMethodInvoker;
@@ -32,38 +30,44 @@ import org.springframework.integration.message.MessagingException;
import org.springframework.util.Assert;
/**
* A {@link ChannelResolver} implementation that invokes the specified method
* on the given object. The method's return value may be a single MessageChannel
* instance, a single String to be interpreted as a channel name, or a Collection
* (or Array) of either type.
* A Message Router that invokes the specified method on the given object. The
* method's return value may be a single MessageChannel instance, a single
* String to be interpreted as a channel name, or a Collection (or Array) of
* either type. If the method returns channel names, then a
* {@link ChannelMapping} is required.
*
* @author Mark Fisher
*/
public class MethodInvokingChannelResolver implements ChannelResolver, ChannelRegistryAware, InitializingBean {
public class MethodInvokingRouter extends AbstractMessageRouter implements InitializingBean {
private final MessageMappingMethodInvoker invoker;
private volatile ChannelRegistry channelRegistry;
private volatile ChannelMapping channelMapping;
public MethodInvokingChannelResolver(Object object, Method method) {
public MethodInvokingRouter(Object object, Method method) {
this.invoker = new MessageMappingMethodInvoker(object, method);
}
public MethodInvokingChannelResolver(Object object, String methodName) {
public MethodInvokingRouter(Object object, String methodName) {
this.invoker = new MessageMappingMethodInvoker(object, methodName);
}
public void setChannelRegistry(ChannelRegistry channelRegistry) {
this.channelRegistry = channelRegistry;
/**
* Provide the ChannelMapping strategy to use for methods that return a
* channel name rather than a {@link MessageChannel} instance.
*/
public void setChannelMapping(ChannelMapping channelMapping) {
this.channelMapping = channelMapping;
}
public void afterPropertiesSet() throws Exception {
this.invoker.afterPropertiesSet();
}
public final Collection<MessageChannel> resolveChannels(Message<?> message) {
@Override
protected final Collection<MessageChannel> resolveChannels(Message<?> message) {
Object result = this.invoker.invokeMethod(message);
if (result == null) {
return null;
@@ -104,9 +108,9 @@ public class MethodInvokingChannelResolver implements ChannelResolver, ChannelRe
}
else if (channelOrName instanceof String) {
String channelName = (String) channelOrName;
Assert.state(this.channelRegistry != null,
"ChannelRegistry is required for resolving channel names");
MessageChannel channel = this.channelRegistry.lookupChannel(channelName);
Assert.state(this.channelMapping != null,
"ChannelMapping is required for resolving channel names");
MessageChannel channel = this.channelMapping.getChannel(channelName);
if (channel == null) {
throw new MessagingException("unable to resolve channel '" + channelName + "'");
}

View File

@@ -24,24 +24,25 @@ import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
/**
* A ChannelResolver implementation that resolves the {@link MessageChannel} based
* on the {@link Message Message's} payload type.
* A Message Router that resolves the {@link MessageChannel} based on the
* {@link Message Message's} payload type.
*
* @author Mark Fisher
*/
public class PayloadTypeChannelResolver extends AbstractSingleChannelResolver {
public class PayloadTypeRouter extends AbstractSingleChannelRouter {
private Map<Class<?>, MessageChannel> channelMappings = new ConcurrentHashMap<Class<?>, MessageChannel>();
private volatile Map<Class<?>, MessageChannel> payloadTypeChannelMap =
new ConcurrentHashMap<Class<?>, MessageChannel>();
public void setChannelMappings(Map<Class<?>, MessageChannel> channelMappings) {
Assert.notNull(channelMappings, "'channelMappings' must not be null");
this.channelMappings = channelMappings;
public void setPayloadTypeChannelMap(Map<Class<?>, MessageChannel> payloadTypeChannelMap) {
Assert.notNull(payloadTypeChannelMap, "payloadTypeChannelMap must not be null");
this.payloadTypeChannelMap = payloadTypeChannelMap;
}
@Override
protected MessageChannel resolveChannel(Message<?> message) {
return this.channelMappings.get(message.getPayload().getClass());
return this.payloadTypeChannelMap.get(message.getPayload().getClass());
}
}

View File

@@ -25,21 +25,19 @@ import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
/**
* A {@link ChannelResolver} implementation that routes to a statically
* configured list of recipients. The recipients are provided as a list of
* {@link MessageChannel} instances. For dynamic recipient lists, consider
* either implementing the {@link ChannelResolver} interface directly or
* extending the {@link AbstractChannelNameResolver} base class.
* A Message Router that sends Messages to a statically configured list of
* recipients. The recipients are provided as a list of {@link MessageChannel}
* instances. For dynamic recipient lists, consider instead using the @Router
* annotation or extending {@link AbstractChannelMappingMessageRouter}.
*
* @author Mark Fisher
*/
public class RecipientListChannelResolver implements ChannelResolver, InitializingBean {
public class RecipientListRouter extends AbstractMessageRouter implements InitializingBean {
private volatile List<MessageChannel> channels;
public void setChannels(List<MessageChannel> channels) {
Assert.notEmpty(channels, "a non-empty channel list is required");
this.channels = channels;
}
@@ -47,7 +45,8 @@ public class RecipientListChannelResolver implements ChannelResolver, Initializi
Assert.notEmpty(this.channels, "a non-empty channel list is required");
}
public Collection<MessageChannel> resolveChannels(Message<?> message) {
@Override
protected Collection<MessageChannel> resolveChannels(Message<?> message) {
return this.channels;
}

View File

@@ -24,21 +24,21 @@ import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
/**
* A ChannelResolver implementation that resolves the {@link MessageChannel} for
* messages whose payload is an Exception. The channel resolution is based upon the
* most specific cause of the error for which a channel-mapping exists.
* A Message Router that resolves the target {@link MessageChannel} for
* messages whose payload is an Exception. The channel resolution is based upon
* the most specific cause of the error for which a channel-mapping exists.
*
* @author Mark Fisher
*/
public class RootCauseErrorMessageChannelResolver extends AbstractSingleChannelResolver {
public class RootCauseErrorMessageRouter extends AbstractSingleChannelRouter {
private Map<Class<? extends Throwable>, MessageChannel> channelMappings =
private volatile Map<Class<? extends Throwable>, MessageChannel> exceptionTypeChannelMap =
new ConcurrentHashMap<Class<? extends Throwable>, MessageChannel>();
public void setChannelMappings(Map<Class<? extends Throwable>, MessageChannel> channelMappings) {
Assert.notNull(channelMappings, "'channelMappings' must not be null");
this.channelMappings = channelMappings;
public void setExceptionTypeChannelMap(Map<Class<? extends Throwable>, MessageChannel> exceptionTypeChannelMap) {
Assert.notNull(exceptionTypeChannelMap, "exceptionTypeChannelMap must not be null");
this.exceptionTypeChannelMap = exceptionTypeChannelMap;
}
@@ -49,7 +49,7 @@ public class RootCauseErrorMessageChannelResolver extends AbstractSingleChannelR
if (payload != null && (payload instanceof Throwable)) {
Throwable mostSpecificCause = (Throwable) payload;
while (mostSpecificCause != null) {
MessageChannel mappedChannel = this.channelMappings.get(mostSpecificCause.getClass());
MessageChannel mappedChannel = this.exceptionTypeChannelMap.get(mostSpecificCause.getClass());
if (mappedChannel != null) {
channel = mappedChannel;
}