Implemented multi-channel routing with support for resolution by channel instance or channel name.

This commit is contained in:
Mark Fisher
2008-01-02 22:22:01 +00:00
parent a4d542a179
commit 57bb42270a
8 changed files with 556 additions and 113 deletions

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2002-2007 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.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.MessagingConfigurationException;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.message.Message;
/**
* Base class for message router implementations.
*
* @author Mark Fisher
*/
public abstract class AbstractRoutingMessageHandler implements MessageHandler, ChannelRegistryAware, InitializingBean {
protected Log logger = LogFactory.getLog(this.getClass());
private ChannelRegistry channelRegistry;
private boolean resolutionRequired = false;
private long timeout = -1;
/**
* 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, set this value to 'true'.
*/
public void setResolutionRequired(boolean resolutionRequired) {
this.resolutionRequired = resolutionRequired;
}
/**
* Set the timeout for sending a message to the resolved channel. By
* default, there is no timeout, meaning the send will block indefinitely.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public void setChannelRegistry(ChannelRegistry channelRegistry) {
this.channelRegistry = channelRegistry;
}
protected ChannelRegistry getChannelRegistry() {
return this.channelRegistry;
}
public final void afterPropertiesSet() {
this.validate();
}
public final Message<?> handle(Message<?> message) {
List<MessageChannel> channels = this.resolveChannels(message);
if (channels == null || channels.size() == 0) {
String errorMessage = "failed to resolve any channel for message";
if (this.resolutionRequired) {
throw new MessageHandlingException(errorMessage);
}
if (logger.isWarnEnabled()) {
logger.warn(errorMessage);
}
return null;
}
for (MessageChannel channel : channels) {
this.sendMesage(message, channel);
}
return null;
}
private void sendMesage(Message<?> message, MessageChannel channel) {
boolean sent = false;
if (timeout < 0) {
sent = channel.send(message);
}
else {
sent = channel.send(message, timeout);
}
if (!sent) {
throw new MessageHandlingException("failed to send message to channel '" + channel.getName() + "'");
}
}
protected abstract void validate() throws MessagingConfigurationException;
protected abstract List<MessageChannel> resolveChannels(Message<?> message);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2002-2007 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.message.Message;
/**
* Strategy interface for content-based routing to multiple channel names.
*
* @author Mark Fisher
*/
public interface MultiChannelNameResolver {
String[] resolve(Message<?> message);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-2007 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.List;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.Message;
/**
* Strategy interface for content-based routing to multiple channels.
*
* @author Mark Fisher
*/
public interface MultiChannelResolver {
List<MessageChannel> resolve(Message<?> message);
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-2007 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.List;
import org.springframework.integration.MessagingConfigurationException;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.Message;
/**
* A router implementation for sending to potentially multiple
* {@link MessageChannel MessageChannels}. Requires either a
* {@link MultiChannelResolver} or {@link MultiChannelNameResolver} strategy
* instance. In the case of the latter, the
* {@link org.springframework.integration.channel.ChannelRegistry} reference
* must also be provided. For convenience, the superclass does implement
* {@link org.springframework.integration.channel.ChannelRegistryAware}.
*
* @author Mark Fisher
*/
public class MultiChannelRouter extends AbstractRoutingMessageHandler {
private MultiChannelResolver channelResolver;
private MultiChannelNameResolver channelNameResolver;
public void setChannelResolver(MultiChannelResolver channelResolver) {
this.channelResolver = channelResolver;
}
public void setChannelNameResolver(MultiChannelNameResolver channelNameResolver) {
this.channelNameResolver = channelNameResolver;
}
@Override
public void validate() {
if (!(this.channelResolver != null ^ this.channelNameResolver != null)) {
throw new MessagingConfigurationException(
"exactly one of 'channelResolver' or 'channelNameResolver' must be provided");
}
if (this.channelNameResolver != null && this.getChannelRegistry() == null) {
throw new MessagingConfigurationException("'channelRegistry' is required when resolving by channel name");
}
}
@Override
public List<MessageChannel> resolveChannels(Message<?> message) {
if (this.channelResolver != null) {
return this.channelResolver.resolve(message);
}
if (this.channelNameResolver == null || this.getChannelRegistry() == null) {
throw new MessagingConfigurationException("router configuration requires either "
+ "a 'channelResolver' or both 'channelNameResolver' and 'channelRegistry'");
}
String[] channelNames = this.channelNameResolver.resolve(message);
if (channelNames == null) {
return null;
}
List<MessageChannel> channels = new ArrayList<MessageChannel>(channelNames.length);
for (String channelName : channelNames) {
MessageChannel channel = this.getChannelRegistry().lookupChannel(channelName);
if (channel != null) {
channels.add(channel);
}
}
return channels;
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2002-2007 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.InitializingBean;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.MessagingConfigurationException;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.message.Message;
/**
* @author Mark Fisher
*/
public class RoutingMessageHandler implements MessageHandler, ChannelRegistryAware, InitializingBean {
private ChannelResolver channelResolver;
private ChannelNameResolver channelNameResolver;
private ChannelRegistry channelRegistry;
private long timeout = -1;
/**
* Set the timeout for sending a message to the resolved channel.
* Default is no timeout, meaning the send will block indefinitely.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public void setChannelResolver(ChannelResolver channelResolver) {
this.channelResolver = channelResolver;
}
public void setChannelNameResolver(ChannelNameResolver channelNameResolver) {
this.channelNameResolver = channelNameResolver;
}
public void setChannelRegistry(ChannelRegistry channelRegistry) {
this.channelRegistry = channelRegistry;
}
public void afterPropertiesSet() {
if(!(this.channelResolver != null ^ this.channelNameResolver != null)) {
throw new MessagingConfigurationException("exactly one of 'channelResolver' or 'channelNameResolver' must be provided");
}
if (this.channelNameResolver != null && this.channelRegistry == null) {
throw new MessagingConfigurationException("'channelRegistry' is required when resolving by channel name");
}
}
public Message<?> handle(Message<?> message) {
MessageChannel channel = this.resolveChannel(message);
if (channel == null) {
throw new MessageHandlingException("failed to resolve channel");
}
boolean sent = false;
if (timeout < 0) {
sent = channel.send(message);
}
else {
sent = channel.send(message, timeout);
}
if (!sent) {
throw new MessageHandlingException(
"failed to send message to channel '" + channel.getName() + "'");
}
return null;
}
private MessageChannel resolveChannel(Message<?> message) {
if (this.channelResolver != null) {
return this.channelResolver.resolve(message);
}
else if (this.channelNameResolver != null && this.channelRegistry != null) {
String channelName = this.channelNameResolver.resolve(message);
return this.channelRegistry.lookupChannel(channelName);
}
throw new MessagingConfigurationException("router configuration requires either " +
"a 'channelResolver' or both 'channelNameResolver' and 'channelRegistry'");
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2007 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.List;
import org.springframework.integration.MessagingConfigurationException;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.Message;
/**
* A router implementation for sending to at most one {@link MessageChannel}.
* Requires either a {@link ChannelResolver} or {@link ChannelNameResolver}
* strategy instance. In the case of the latter, the
* {@link org.springframework.integration.channel.ChannelRegistry} reference
* must also be provided. For convenience, the superclass does implement
* {@link org.springframework.integration.channel.ChannelRegistryAware}.
*
* @author Mark Fisher
*/
public class SingleChannelRouter extends AbstractRoutingMessageHandler {
private ChannelResolver channelResolver;
private ChannelNameResolver channelNameResolver;
public void setChannelResolver(ChannelResolver channelResolver) {
this.channelResolver = channelResolver;
}
public void setChannelNameResolver(ChannelNameResolver channelNameResolver) {
this.channelNameResolver = channelNameResolver;
}
@Override
public void validate() {
if (!(this.channelResolver != null ^ this.channelNameResolver != null)) {
throw new MessagingConfigurationException(
"exactly one of 'channelResolver' or 'channelNameResolver' must be provided");
}
if (this.channelNameResolver != null && this.getChannelRegistry() == null) {
throw new MessagingConfigurationException("'channelRegistry' is required when resolving by channel name");
}
}
@Override
public List<MessageChannel> resolveChannels(Message<?> message) {
List<MessageChannel> channels = new ArrayList<MessageChannel>();
MessageChannel channel = this.resolveChannel(message);
if (channel != null) {
channels.add(channel);
}
return channels;
}
private MessageChannel resolveChannel(Message<?> message) {
if (this.channelResolver != null) {
return this.channelResolver.resolve(message);
}
if (this.channelNameResolver == null || this.getChannelRegistry() == null) {
throw new MessagingConfigurationException("router configuration requires either "
+ "a 'channelResolver' or both 'channelNameResolver' and 'channelRegistry'");
}
String channelName = this.channelNameResolver.resolve(message);
return this.getChannelRegistry().lookupChannel(channelName);
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2002-2007 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.MessagingConfigurationException;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.DefaultChannelRegistry;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.PointToPointChannel;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.StringMessage;
/**
* @author Mark Fisher
*/
public class MultiChannelRouterTests {
@Test
public void testRoutingWithChannelResolver() {
final PointToPointChannel channel1 = new PointToPointChannel();
final PointToPointChannel channel2 = new PointToPointChannel();
MultiChannelResolver channelResolver = new MultiChannelResolver() {
public List<MessageChannel> resolve(Message<?> message) {
List<MessageChannel> channels = new ArrayList<MessageChannel>();
channels.add(channel1);
channels.add(channel2);
return channels;
}
};
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelResolver(channelResolver);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
Message<?> result1 = channel1.receive(25);
assertNotNull(result1);
assertEquals("test", result1.getPayload());
Message<?> result2 = channel2.receive(25);
assertNotNull(result2);
assertEquals("test", result2.getPayload());
}
@Test
public void testRoutingWithChannelNameResolver() {
MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() {
public String[] resolve(Message<?> message) {
return new String[] {"channel1", "channel2"};
}
};
PointToPointChannel channel1 = new PointToPointChannel();
PointToPointChannel channel2 = new PointToPointChannel();
ChannelRegistry channelRegistry = new DefaultChannelRegistry();
channelRegistry.registerChannel("channel1", channel1);
channelRegistry.registerChannel("channel2", channel2);
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelNameResolver(channelNameResolver);
router.setChannelRegistry(channelRegistry);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
Message<?> result1 = channel1.receive(25);
assertNotNull(result1);
assertEquals("test", result1.getPayload());
Message<?> result2 = channel2.receive(25);
assertNotNull(result2);
assertEquals("test", result2.getPayload());
}
@Test(expected=MessagingConfigurationException.class)
public void testConfiguringBothChannelResolverAndChannelNameResolverIsNotAllowed() {
MultiChannelResolver channelResolver = new MultiChannelResolver() {
public List<MessageChannel> resolve(Message<?> message) {
return null;
}
};
MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() {
public String[] resolve(Message<?> message) {
return null;
}
};
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelResolver(channelResolver);
router.setChannelNameResolver(channelNameResolver);
router.afterPropertiesSet();
}
public void testChannelResolutionFailureIgnoredByDefault() {
MultiChannelResolver channelResolver = new MultiChannelResolver() {
public List<MessageChannel> resolve(Message<?> message) {
return null;
}
};
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelResolver(channelResolver);
router.setResolutionRequired(true);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
@Test(expected=MessageHandlingException.class)
public void testChannelResolutionFailureThrowsExceptionWhenResolutionRequired() {
MultiChannelResolver channelResolver = new MultiChannelResolver() {
public List<MessageChannel> resolve(Message<?> message) {
return null;
}
};
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelResolver(channelResolver);
router.setResolutionRequired(true);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
public void testChannelNameResolutionFailureIgnoredByDefault() {
MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() {
public String[] resolve(Message<?> message) {
return new String[] {"noSuchChannel"};
}
};
ChannelRegistry channelRegistry = new DefaultChannelRegistry();
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelNameResolver(channelNameResolver);
router.setChannelRegistry(channelRegistry);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
@Test(expected=MessageHandlingException.class)
public void testChannelNameResolutionFailureThrowsExceptionWhenResolutionRequired() {
MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() {
public String[] resolve(Message<?> message) {
return new String[] {"noSuchChannel"};
}
};
ChannelRegistry channelRegistry = new DefaultChannelRegistry();
MultiChannelRouter router = new MultiChannelRouter();
router.setChannelNameResolver(channelNameResolver);
router.setChannelRegistry(channelRegistry);
router.setResolutionRequired(true);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
}

View File

@@ -33,7 +33,7 @@ import org.springframework.integration.message.StringMessage;
/**
* @author Mark Fisher
*/
public class RoutingMessageHandlerTests {
public class SingleChannelRouterTests {
@Test
public void testRoutingWithChannelResolver() {
@@ -43,7 +43,7 @@ public class RoutingMessageHandlerTests {
return channel;
}
};
RoutingMessageHandler router = new RoutingMessageHandler();
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelResolver(channelResolver);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
@@ -63,7 +63,7 @@ public class RoutingMessageHandlerTests {
PointToPointChannel channel = new PointToPointChannel();
ChannelRegistry channelRegistry = new DefaultChannelRegistry();
channelRegistry.registerChannel("testChannel", channel);
RoutingMessageHandler router = new RoutingMessageHandler();
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelNameResolver(channelNameResolver);
router.setChannelRegistry(channelRegistry);
router.afterPropertiesSet();
@@ -86,37 +86,49 @@ public class RoutingMessageHandlerTests {
return "";
}
};
RoutingMessageHandler router = new RoutingMessageHandler();
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelResolver(channelResolver);
router.setChannelNameResolver(channelNameResolver);
router.afterPropertiesSet();
}
@Test(expected=MessageHandlingException.class)
public void testChannelResolutionFailure() {
public void testChannelResolutionFailureIgnoredByDefault() {
ChannelResolver channelResolver = new ChannelResolver() {
public MessageChannel resolve(Message<?> message) {
return null;
}
};
RoutingMessageHandler router = new RoutingMessageHandler();
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelResolver(channelResolver);
router.setResolutionRequired(true);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
@Test(expected=MessageHandlingException.class)
public void testChannelNameResolutionFailure() {
public void testChannelResolutionFailureThrowsExceptionWhenResolutionRequired() {
ChannelResolver channelResolver = new ChannelResolver() {
public MessageChannel resolve(Message<?> message) {
return null;
}
};
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelResolver(channelResolver);
router.setResolutionRequired(true);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
public void testChannelNameResolutionFailureIgnoredByDefault() {
ChannelNameResolver channelNameResolver = new ChannelNameResolver() {
public String resolve(Message<?> message) {
return "noSuchChannel";
}
};
PointToPointChannel channel = new PointToPointChannel();
ChannelRegistry channelRegistry = new DefaultChannelRegistry();
channelRegistry.registerChannel("testChannel", channel);
RoutingMessageHandler router = new RoutingMessageHandler();
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelNameResolver(channelNameResolver);
router.setChannelRegistry(channelRegistry);
router.afterPropertiesSet();
@@ -124,4 +136,21 @@ public class RoutingMessageHandlerTests {
router.handle(message);
}
@Test(expected=MessageHandlingException.class)
public void testChannelNameResolutionFailureThrowsExceptionWhenResolutionRequired() {
ChannelNameResolver channelNameResolver = new ChannelNameResolver() {
public String resolve(Message<?> message) {
return "noSuchChannel";
}
};
ChannelRegistry channelRegistry = new DefaultChannelRegistry();
SingleChannelRouter router = new SingleChannelRouter();
router.setChannelNameResolver(channelNameResolver);
router.setChannelRegistry(channelRegistry);
router.setResolutionRequired(true);
router.afterPropertiesSet();
Message<String> message = new StringMessage("123", "test");
router.handle(message);
}
}