From 89e990f661f58d2e81679ffffc22f65bcc774afa Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Mon, 21 Jan 2008 16:34:20 +0000 Subject: [PATCH] DefaultMessageEndpoint now provides a ReplyHandler callback for ConcurrentHandlers. --- .../integration/channel/SimpleChannel.java | 5 +- .../dispatcher/DispatcherTask.java | 2 + .../endpoint/DefaultMessageEndpoint.java | 45 ++- .../handler/ConcurrentHandler.java | 13 +- .../MessageHandlerNotRunningException.java | 2 +- ...sageHandlerRejectedExecutionException.java | 2 +- .../integration/handler/ReplyHandler.java | 31 ++ .../MessageSelectorRejectedException.java | 2 +- .../config/ChannelParserTests.java | 2 + .../integration/config/channelParserTests.xml | 6 +- .../ChannelPollingMessageRetrieverTests.java | 100 ++++++ .../DefaultMessageDispatcherTests.java | 2 +- .../dispatcher/DispatcherTaskTests.java | 79 +++++ .../endpoint/DefaultMessageEndpointTests.java | 324 ++++++++++++++++++ .../integration/handler/TestHandlers.java | 1 - 15 files changed, 592 insertions(+), 24 deletions(-) rename spring-integration-core/src/main/java/org/springframework/integration/{dispatcher => handler}/MessageHandlerNotRunningException.java (94%) rename spring-integration-core/src/main/java/org/springframework/integration/{dispatcher => handler}/MessageHandlerRejectedExecutionException.java (96%) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/ReplyHandler.java rename spring-integration-core/src/main/java/org/springframework/integration/{dispatcher => message/selector}/MessageSelectorRejectedException.java (95%) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/dispatcher/ChannelPollingMessageRetrieverTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DispatcherTaskTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/SimpleChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/SimpleChannel.java index 4b8bc462ad..159cba39c0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/SimpleChannel.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/SimpleChannel.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.BeanNameAware; import org.springframework.integration.dispatcher.DispatcherPolicy; import org.springframework.integration.message.Message; +import org.springframework.util.Assert; /** * Simple implementation of a message channel. Each {@link Message} is @@ -33,7 +34,7 @@ import org.springframework.integration.message.Message; */ public class SimpleChannel implements MessageChannel, BeanNameAware { - private static final int DEFAULT_CAPACITY = 25; + private static final int DEFAULT_CAPACITY = 100; private String name; @@ -110,6 +111,7 @@ public class SimpleChannel implements MessageChannel, BeanNameAware { * false if the sending thread is interrupted. */ public boolean send(Message message) { + Assert.notNull(message, "'message' must not be null"); try { queue.put(message); return true; @@ -134,6 +136,7 @@ public class SimpleChannel implements MessageChannel, BeanNameAware { * time or the sending thread is interrupted. */ public boolean send(Message message, long timeout) { + Assert.notNull(message, "'message' must not be null"); try { if (timeout > 0) { return this.queue.offer(message, timeout, TimeUnit.MILLISECONDS); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/DispatcherTask.java b/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/DispatcherTask.java index ab2f7176e7..945e4e4417 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/DispatcherTask.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/DispatcherTask.java @@ -29,7 +29,9 @@ import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.MessageHandlerNotRunningException; import org.springframework.integration.message.Message; +import org.springframework.integration.message.selector.MessageSelectorRejectedException; import org.springframework.integration.scheduling.MessagingTask; import org.springframework.integration.scheduling.Schedule; import org.springframework.util.Assert; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/DefaultMessageEndpoint.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/DefaultMessageEndpoint.java index b8a0e05c2d..e876cb8eb8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/DefaultMessageEndpoint.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/DefaultMessageEndpoint.java @@ -28,15 +28,18 @@ import org.springframework.integration.bus.Subscription; import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.ChannelRegistryAware; import org.springframework.integration.channel.MessageChannel; -import org.springframework.integration.dispatcher.MessageHandlerNotRunningException; -import org.springframework.integration.dispatcher.MessageHandlerRejectedExecutionException; -import org.springframework.integration.dispatcher.MessageSelectorRejectedException; import org.springframework.integration.handler.ConcurrentHandler; import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.MessageHandlerNotRunningException; +import org.springframework.integration.handler.MessageHandlerRejectedExecutionException; +import org.springframework.integration.handler.ReplyHandler; import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHeader; import org.springframework.integration.message.selector.MessageSelector; +import org.springframework.integration.message.selector.MessageSelectorRejectedException; import org.springframework.integration.util.ErrorHandler; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Default implementation of the {@link MessageEndpoint} interface. @@ -57,6 +60,8 @@ public class DefaultMessageEndpoint implements MessageEndpoint, ChannelRegistryA private ErrorHandler errorHandler; + private ReplyHandler replyHandler = new EndpointReplyHandler(); + private String defaultOutputChannelName; private ChannelRegistry channelRegistry; @@ -145,7 +150,7 @@ public class DefaultMessageEndpoint implements MessageEndpoint, ChannelRegistryA } public void afterPropertiesSet() { - if (this.concurrencyPolicy != null) { + if (this.concurrencyPolicy != null || this.handler instanceof ConcurrentHandler) { if (!(this.handler instanceof ConcurrentHandler)) { this.handler = new ConcurrentHandler(this.handler, this.concurrencyPolicy); } @@ -153,6 +158,7 @@ public class DefaultMessageEndpoint implements MessageEndpoint, ChannelRegistryA if (this.errorHandler != null) { concurrentHandler.setErrorHandler(this.errorHandler); } + concurrentHandler.setReplyHandler(this.replyHandler); concurrentHandler.afterPropertiesSet(); } this.initialized = true; @@ -206,13 +212,7 @@ public class DefaultMessageEndpoint implements MessageEndpoint, ChannelRegistryA try { Message replyMessage = handler.handle(message); if (replyMessage != null) { - MessageChannel replyChannel = this.resolveReplyChannel(message); - if (replyChannel == null) { - throw new MessageHandlingException("Unable to determine reply channel for message. " - + "Provide a 'replyChannelName' in the message header or a 'defaultOutputChannelName' " - + "on the message endpoint."); - } - replyChannel.send(replyMessage); + this.replyHandler.handle(replyMessage, message.getHeader()); } } catch (MessageHandlerRejectedExecutionException e) { @@ -228,12 +228,11 @@ public class DefaultMessageEndpoint implements MessageEndpoint, ChannelRegistryA return null; } - private MessageChannel resolveReplyChannel(Message message) { + private MessageChannel resolveReplyChannel(String replyChannelName) { if (this.channelRegistry == null) { return null; } - String replyChannelName = message.getHeader().getReplyChannelName(); - if (replyChannelName != null && replyChannelName.trim().length() > 0) { + if (StringUtils.hasText(replyChannelName)) { return this.channelRegistry.lookupChannel(replyChannelName); } if (this.defaultOutputChannelName != null) { @@ -242,4 +241,22 @@ public class DefaultMessageEndpoint implements MessageEndpoint, ChannelRegistryA return null; } + + private class EndpointReplyHandler implements ReplyHandler { + + public void handle(Message replyMessage, MessageHeader originalMessageHeader) { + if (replyMessage == null) { + return; + } + String replyChannelName = originalMessageHeader.getReplyChannelName(); + MessageChannel replyChannel = resolveReplyChannel(replyChannelName); + if (replyChannel == null) { + throw new MessageHandlingException("Unable to determine reply channel for message. " + + "Provide a 'replyChannelName' in the message header or a 'defaultOutputChannelName' " + + "on the message endpoint."); + } + replyChannel.send(replyMessage); + } + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ConcurrentHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ConcurrentHandler.java index 3e41d021c6..d6c0a04b43 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/ConcurrentHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ConcurrentHandler.java @@ -21,8 +21,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.Lifecycle; -import org.springframework.integration.dispatcher.MessageHandlerNotRunningException; -import org.springframework.integration.dispatcher.MessageHandlerRejectedExecutionException; import org.springframework.integration.endpoint.ConcurrencyPolicy; import org.springframework.integration.message.Message; import org.springframework.integration.util.ErrorHandler; @@ -51,6 +49,8 @@ public class ConcurrentHandler implements MessageHandler, Lifecycle, Initializin private ErrorHandler errorHandler; + private ReplyHandler replyHandler; + private volatile boolean running; private Object lifecycleMonitor = new Object(); @@ -83,6 +83,10 @@ public class ConcurrentHandler implements MessageHandler, Lifecycle, Initializin this.errorHandler = errorHandler; } + public void setReplyHandler(ReplyHandler replyHandler) { + this.replyHandler = replyHandler; + } + public void afterPropertiesSet() { refreshExecutor(); } @@ -173,7 +177,10 @@ public class ConcurrentHandler implements MessageHandler, Lifecycle, Initializin public void run() { try { - handler.handle(this.message); + Message reply = handler.handle(this.message); + if (replyHandler != null) { + replyHandler.handle(reply, this.message.getHeader()); + } } catch (Throwable t) { if (errorHandler != null) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageHandlerNotRunningException.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerNotRunningException.java similarity index 94% rename from spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageHandlerNotRunningException.java rename to spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerNotRunningException.java index 0d63fa7fed..b72b582502 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageHandlerNotRunningException.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerNotRunningException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.dispatcher; +package org.springframework.integration.handler; import org.springframework.integration.MessageHandlingException; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageHandlerRejectedExecutionException.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerRejectedExecutionException.java similarity index 96% rename from spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageHandlerRejectedExecutionException.java rename to spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerRejectedExecutionException.java index 07c3b76d7e..ebd4ac608b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageHandlerRejectedExecutionException.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerRejectedExecutionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.dispatcher; +package org.springframework.integration.handler; import org.springframework.integration.MessageHandlingException; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ReplyHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ReplyHandler.java new file mode 100644 index 0000000000..48d155ed2f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ReplyHandler.java @@ -0,0 +1,31 @@ +/* + * 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.handler; + +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHeader; + +/** + * Strategy interface for handling reply messages. + * + * @author Mark Fisher + */ +public interface ReplyHandler { + + void handle(Message replyMessage, MessageHeader originalMessageHeader); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageSelectorRejectedException.java b/spring-integration-core/src/main/java/org/springframework/integration/message/selector/MessageSelectorRejectedException.java similarity index 95% rename from spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageSelectorRejectedException.java rename to spring-integration-core/src/main/java/org/springframework/integration/message/selector/MessageSelectorRejectedException.java index b67fd89132..d6f1b3d8b6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dispatcher/MessageSelectorRejectedException.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/selector/MessageSelectorRejectedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.dispatcher; +package org.springframework.integration.message.selector; import org.springframework.integration.MessageHandlingException; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java index e25623cbff..6ea86e0dcb 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java @@ -120,6 +120,7 @@ public class ChannelParserTests { assertEquals(DispatcherPolicy.DEFAULT_RECEIVE_TIMEOUT, dispatcherPolicy.getReceiveTimeout()); assertEquals(DispatcherPolicy.DEFAULT_REJECTION_LIMIT, dispatcherPolicy.getRejectionLimit()); assertEquals(DispatcherPolicy.DEFAULT_RETRY_INTERVAL, dispatcherPolicy.getRetryInterval()); + assertTrue(dispatcherPolicy.getShouldFailOnRejectionLimit()); } @Test @@ -133,6 +134,7 @@ public class ChannelParserTests { assertEquals(77, dispatcherPolicy.getReceiveTimeout()); assertEquals(777, dispatcherPolicy.getRejectionLimit()); assertEquals(7777, dispatcherPolicy.getRetryInterval()); + assertFalse(dispatcherPolicy.getShouldFailOnRejectionLimit()); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml index 4ab0159f98..6e06361a2c 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml @@ -16,7 +16,11 @@ - + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/ChannelPollingMessageRetrieverTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/ChannelPollingMessageRetrieverTests.java new file mode 100644 index 0000000000..30cdc53d2a --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/ChannelPollingMessageRetrieverTests.java @@ -0,0 +1,100 @@ +/* + * 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.dispatcher; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.Iterator; + +import org.junit.Test; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.SimpleChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class ChannelPollingMessageRetrieverTests { + + @Test + public void testSingleMessagePerRetrieval() { + DispatcherPolicy dispatcherPolicy = new DispatcherPolicy(); + dispatcherPolicy.setReceiveTimeout(0); + MessageChannel channel = new SimpleChannel(dispatcherPolicy); + ChannelPollingMessageRetriever retriever = new ChannelPollingMessageRetriever(channel); + Collection> results = retriever.retrieveMessages(); + assertTrue(results.isEmpty()); + channel.send(new StringMessage("test1")); + channel.send(new StringMessage("test2")); + results = retriever.retrieveMessages(); + assertEquals(1, results.size()); + assertEquals("test1", results.iterator().next().getPayload()); + results = retriever.retrieveMessages(); + assertEquals(1, results.size()); + assertEquals("test2", results.iterator().next().getPayload()); + } + + @Test + public void testMultipleMessagesPerRetrieval() { + DispatcherPolicy dispatcherPolicy = new DispatcherPolicy(); + dispatcherPolicy.setReceiveTimeout(0); + dispatcherPolicy.setMaxMessagesPerTask(2); + MessageChannel channel = new SimpleChannel(dispatcherPolicy); + ChannelPollingMessageRetriever retriever = new ChannelPollingMessageRetriever(channel); + Collection> results = retriever.retrieveMessages(); + assertTrue(results.isEmpty()); + channel.send(new StringMessage("test1")); + channel.send(new StringMessage("test2")); + channel.send(new StringMessage("test3")); + results = retriever.retrieveMessages(); + assertEquals(2, results.size()); + Iterator> iter = results.iterator(); + assertEquals("test1", iter.next().getPayload()); + assertEquals("test2", iter.next().getPayload()); + results = retriever.retrieveMessages(); + assertEquals(1, results.size()); + assertEquals("test3", results.iterator().next().getPayload()); + } + + @Test + public void testMaxMessagesConfiguredDynamically() { + DispatcherPolicy dispatcherPolicy = new DispatcherPolicy(); + dispatcherPolicy.setReceiveTimeout(0); + dispatcherPolicy.setMaxMessagesPerTask(1); + MessageChannel channel = new SimpleChannel(dispatcherPolicy); + ChannelPollingMessageRetriever retriever = new ChannelPollingMessageRetriever(channel); + Collection> results = retriever.retrieveMessages(); + assertTrue(results.isEmpty()); + channel.send(new StringMessage("test1")); + channel.send(new StringMessage("test2")); + channel.send(new StringMessage("test3")); + results = retriever.retrieveMessages(); + assertEquals(1, results.size()); + assertEquals("test1", results.iterator().next().getPayload()); + channel.getDispatcherPolicy().setMaxMessagesPerTask(5); + results = retriever.retrieveMessages(); + assertEquals(2, results.size()); + Iterator> iter = results.iterator(); + assertEquals("test2", iter.next().getPayload()); + assertEquals("test3", iter.next().getPayload()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DefaultMessageDispatcherTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DefaultMessageDispatcherTests.java index f0b6872449..db1d6bfedc 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DefaultMessageDispatcherTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DefaultMessageDispatcherTests.java @@ -29,12 +29,12 @@ import org.junit.Test; import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.channel.SimpleChannel; import org.springframework.integration.dispatcher.DefaultMessageDispatcher; -import org.springframework.integration.dispatcher.MessageHandlerRejectedExecutionException; import org.springframework.integration.endpoint.ConcurrencyPolicy; import org.springframework.integration.endpoint.DefaultMessageEndpoint; import org.springframework.integration.handler.ConcurrentHandler; import org.springframework.integration.handler.InterceptingMessageHandler; import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.MessageHandlerRejectedExecutionException; import org.springframework.integration.handler.TestHandlers; import org.springframework.integration.message.ErrorMessage; import org.springframework.integration.message.Message; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DispatcherTaskTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DispatcherTaskTests.java new file mode 100644 index 0000000000..994333794b --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dispatcher/DispatcherTaskTests.java @@ -0,0 +1,79 @@ +/* + * 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.dispatcher; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.SimpleChannel; +import org.springframework.integration.handler.TestHandlers; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class DispatcherTaskTests { + + @Test + public void testSimpleDispatch() throws InterruptedException { + MessageChannel channel = new SimpleChannel(); + DispatcherTask task = new DispatcherTask(channel); + final CountDownLatch latch = new CountDownLatch(1); + task.addHandler(TestHandlers.countDownHandler(latch)); + task.dispatchMessage(new StringMessage("test")); + latch.await(500, TimeUnit.MILLISECONDS); + assertEquals(0, latch.getCount()); + } + + @Test + public void testDispatchWithPointToPointChannel() throws InterruptedException { + MessageChannel channel = new SimpleChannel(new DispatcherPolicy(false)); + DispatcherTask task = new DispatcherTask(channel); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicInteger counter1 = new AtomicInteger(); + final AtomicInteger counter2 = new AtomicInteger(); + task.addHandler(TestHandlers.countingCountDownHandler(counter1, latch)); + task.addHandler(TestHandlers.countingCountDownHandler(counter2, latch)); + task.dispatchMessage(new StringMessage("test")); + latch.await(500, TimeUnit.MILLISECONDS); + assertEquals(0, latch.getCount()); + assertEquals("only 1 handler should have received the message", 1, counter1.get() + counter2.get()); + } + + @Test + public void testDispatchWithPublishSubscribeChannel() throws InterruptedException { + MessageChannel channel = new SimpleChannel(new DispatcherPolicy(true)); + DispatcherTask task = new DispatcherTask(channel); + final CountDownLatch latch = new CountDownLatch(2); + final AtomicInteger counter1 = new AtomicInteger(); + final AtomicInteger counter2 = new AtomicInteger(); + task.addHandler(TestHandlers.countingCountDownHandler(counter1, latch)); + task.addHandler(TestHandlers.countingCountDownHandler(counter2, latch)); + task.dispatchMessage(new StringMessage("test")); + latch.await(500, TimeUnit.MILLISECONDS); + assertEquals(0, latch.getCount()); + assertEquals(1, counter1.get()); + assertEquals(1, counter2.get()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/DefaultMessageEndpointTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/DefaultMessageEndpointTests.java index 71170c8539..dbe4c420de 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/DefaultMessageEndpointTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/DefaultMessageEndpointTests.java @@ -18,6 +18,12 @@ package org.springframework.integration.endpoint; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; @@ -25,9 +31,15 @@ import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.DefaultChannelRegistry; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.channel.SimpleChannel; +import org.springframework.integration.handler.ConcurrentHandler; import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.MessageHandlerNotRunningException; +import org.springframework.integration.handler.TestHandlers; import org.springframework.integration.message.Message; import org.springframework.integration.message.StringMessage; +import org.springframework.integration.message.selector.MessageSelector; +import org.springframework.integration.message.selector.MessageSelectorRejectedException; +import org.springframework.integration.util.ErrorHandler; /** * @author Mark Fisher @@ -79,4 +91,316 @@ public class DefaultMessageEndpointTests { assertEquals("hello test", reply.getPayload()); } + @Test + public void testCustomErrorHandler() throws InterruptedException { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setConcurrencyPolicy(new ConcurrencyPolicy(1, 1)); + final CountDownLatch latch = new CountDownLatch(2); + endpoint.setErrorHandler(new ErrorHandler() { + public void handle(Throwable t) { + latch.countDown(); + } + }); + endpoint.setHandler(TestHandlers.rejectingCountDownHandler(latch)); + endpoint.start(); + endpoint.handle(new StringMessage("test")); + latch.await(500, TimeUnit.MILLISECONDS); + assertEquals("both handler and errorHandler should have been invoked", 0, latch.getCount()); + } + + @Test + public void testConcurrentHandlerWithDefaultReplyChannel() throws InterruptedException { + MessageChannel replyChannel = new SimpleChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("replyChannel", replyChannel); + final CountDownLatch latch = new CountDownLatch(1); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + latch.countDown(); + return new StringMessage("123", "hello " + message.getPayload()); + } + }; + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setChannelRegistry(channelRegistry); + endpoint.setHandler(new ConcurrentHandler(handler)); + endpoint.setDefaultOutputChannelName("replyChannel"); + endpoint.start(); + endpoint.handle(new StringMessage(1, "test")); + endpoint.stop(); + latch.await(200, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked within allotted time", 0, latch.getCount()); + Message reply = replyChannel.receive(100); + assertNotNull(reply); + assertEquals("hello test", reply.getPayload()); + } + + @Test + public void testHandlerReturnsNull() throws InterruptedException { + MessageChannel replyChannel = new SimpleChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("replyChannel", replyChannel); + final CountDownLatch latch = new CountDownLatch(1); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + latch.countDown(); + return null; + } + }; + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setChannelRegistry(channelRegistry); + endpoint.setHandler(handler); + endpoint.setDefaultOutputChannelName("replyChannel"); + endpoint.start(); + endpoint.handle(new StringMessage(1, "test")); + endpoint.stop(); + latch.await(200, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked within allotted time", 0, latch.getCount()); + Message reply = replyChannel.receive(0); + assertNull(reply); + } + + @Test + public void testConcurrentHandlerReturnsNull() throws InterruptedException { + MessageChannel replyChannel = new SimpleChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("replyChannel", replyChannel); + final CountDownLatch latch = new CountDownLatch(1); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + latch.countDown(); + return null; + } + }; + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setChannelRegistry(channelRegistry); + endpoint.setHandler(new ConcurrentHandler(handler)); + endpoint.setDefaultOutputChannelName("replyChannel"); + endpoint.start(); + endpoint.handle(new StringMessage(1, "test")); + endpoint.stop(); + latch.await(200, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked within allotted time", 0, latch.getCount()); + Message reply = replyChannel.receive(0); + assertNull(reply); + } + + @Test + public void testConcurrentHandlerWithExplicitReplyChannel() throws InterruptedException { + MessageChannel replyChannel = new SimpleChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("replyChannel", replyChannel); + final CountDownLatch latch = new CountDownLatch(1); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + latch.countDown(); + return new StringMessage("123", "hello " + message.getPayload()); + } + }; + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setChannelRegistry(channelRegistry); + endpoint.setHandler(new ConcurrentHandler(handler)); + endpoint.start(); + StringMessage message = new StringMessage(1, "test"); + message.getHeader().setReplyChannelName("replyChannel"); + endpoint.handle(message); + endpoint.stop(); + latch.await(200, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked within allotted time", 0, latch.getCount()); + Message reply = replyChannel.receive(100); + assertNotNull(reply); + assertEquals("hello test", reply.getPayload()); + } + + @Test + public void testGeneratedConcurrentHandlerWithDefaultReplyChannel() throws InterruptedException { + MessageChannel replyChannel = new SimpleChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("replyChannel", replyChannel); + final CountDownLatch latch = new CountDownLatch(1); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + latch.countDown(); + return new StringMessage("123", "hello " + message.getPayload()); + } + }; + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setChannelRegistry(channelRegistry); + endpoint.setHandler(handler); + endpoint.setConcurrencyPolicy(new ConcurrencyPolicy(3, 14)); + endpoint.setDefaultOutputChannelName("replyChannel"); + endpoint.start(); + endpoint.handle(new StringMessage(1, "test")); + endpoint.stop(); + latch.await(200, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked within allotted time", 0, latch.getCount()); + Message reply = replyChannel.receive(100); + assertNotNull(reply); + assertEquals("hello test", reply.getPayload()); + } + + @Test + public void testGeneratedConcurrentHandlerWithExplicitReplyChannel() throws InterruptedException { + MessageChannel replyChannel = new SimpleChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("replyChannel", replyChannel); + final CountDownLatch latch = new CountDownLatch(1); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + latch.countDown(); + return new StringMessage("123", "hello " + message.getPayload()); + } + }; + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.setChannelRegistry(channelRegistry); + endpoint.setHandler(handler); + endpoint.setConcurrencyPolicy(new ConcurrencyPolicy(3, 14)); + endpoint.start(); + StringMessage message = new StringMessage(1, "test"); + message.getHeader().setReplyChannelName("replyChannel"); + endpoint.handle(message); + endpoint.stop(); + latch.await(200, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked within allotted time", 0, latch.getCount()); + Message reply = replyChannel.receive(100); + assertNotNull(reply); + assertEquals("hello test", reply.getPayload()); + } + + @Test(expected=MessageHandlerNotRunningException.class) + public void testEndpointDoesNotHandleMessagesWhenNotYetStarted() { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.handle(new StringMessage("test")); + } + + @Test + public void testEndpointDoesNotHandleMessagesAfterBeingStopped() { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + AtomicInteger counter = new AtomicInteger(); + boolean exceptionThrown = false; + try { + endpoint.start(); + endpoint.setHandler(TestHandlers.countingHandler(counter)); + endpoint.handle(new StringMessage("test1")); + endpoint.stop(); + endpoint.handle(new StringMessage("test2")); + } + catch (MessageHandlerNotRunningException e) { + exceptionThrown = true; + } + assertEquals("handler should have been invoked exactly once", 1, counter.get()); + assertTrue(exceptionThrown); + } + + @Test(expected=MessageSelectorRejectedException.class) + public void testEndpointWithSelectorRejecting() { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + return false; + } + }); + endpoint.start(); + endpoint.handle(new StringMessage("test")); + } + + @Test + public void testEndpointWithSelectorAccepting() throws InterruptedException { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + return true; + } + }); + CountDownLatch latch = new CountDownLatch(1); + endpoint.setHandler(TestHandlers.countDownHandler(latch)); + endpoint.start(); + endpoint.handle(new StringMessage("test")); + latch.await(100, TimeUnit.MILLISECONDS); + assertEquals("handler should have been invoked", 0, latch.getCount()); + endpoint.stop(); + } + + @Test + public void testEndpointWithMultipleSelectorsAndFirstRejects() { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + boolean exceptionThrown = false; + final AtomicInteger counter = new AtomicInteger(); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + counter.incrementAndGet(); + return false; + } + }); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + counter.incrementAndGet(); + return true; + } + }); + endpoint.setHandler(TestHandlers.countingHandler(counter)); + endpoint.start(); + try { + endpoint.handle(new StringMessage("test")); + } + catch (MessageSelectorRejectedException e) { + exceptionThrown = true; + } + assertEquals("only the first selector should have been invoked", 1, counter.get()); + assertTrue(exceptionThrown); + endpoint.stop(); + } + + @Test + public void testEndpointWithMultipleSelectorsAndFirstAccepts() { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + boolean exceptionThrown = false; + final AtomicInteger counter = new AtomicInteger(); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + counter.incrementAndGet(); + return true; + } + }); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + counter.incrementAndGet(); + return false; + } + }); + endpoint.setHandler(TestHandlers.countingHandler(counter)); + endpoint.start(); + try { + endpoint.handle(new StringMessage("test")); + } + catch (MessageSelectorRejectedException e) { + exceptionThrown = true; + } + assertEquals("both selectors should have been invoked but not the handler", 2, counter.get()); + assertTrue(exceptionThrown); + endpoint.stop(); + } + + @Test + public void testEndpointWithMultipleSelectorsAndBothAccept() { + DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(); + final AtomicInteger counter = new AtomicInteger(); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + counter.incrementAndGet(); + return true; + } + }); + endpoint.addMessageSelector(new MessageSelector() { + public boolean accept(Message message) { + counter.incrementAndGet(); + return true; + } + }); + endpoint.setHandler(TestHandlers.countingHandler(counter)); + endpoint.start(); + endpoint.handle(new StringMessage("test")); + assertEquals("both selectors and handler should have been invoked", 3, counter.get()); + endpoint.stop(); + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/TestHandlers.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/TestHandlers.java index f0d115b033..9e0de65ded 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/TestHandlers.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/TestHandlers.java @@ -19,7 +19,6 @@ package org.springframework.integration.handler; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.integration.dispatcher.MessageHandlerRejectedExecutionException; import org.springframework.integration.message.Message; /**