diff --git a/org.springframework.integration/.classpath b/org.springframework.integration/.classpath index 1e48e6bad0..dc64496535 100644 --- a/org.springframework.integration/.classpath +++ b/org.springframework.integration/.classpath @@ -16,5 +16,7 @@ + + diff --git a/org.springframework.integration/ivy.xml b/org.springframework.integration/ivy.xml index f0caac55ee..7ddd6efaac 100644 --- a/org.springframework.integration/ivy.xml +++ b/org.springframework.integration/ivy.xml @@ -27,6 +27,7 @@ + \ No newline at end of file diff --git a/org.springframework.integration/pom.xml b/org.springframework.integration/pom.xml index d444aff62d..5f531d0ffa 100644 --- a/org.springframework.integration/pom.xml +++ b/org.springframework.integration/pom.xml @@ -9,14 +9,20 @@ 1.0.1.RELEASE - org.springframework - spring-aop - ${spring.version} + org.springframework + spring-aop + ${spring.version} - org.springframework - spring-tx - ${spring.version} + org.springframework + spring-tx + ${spring.version} + + + org.mockito + com.springsource.org.mockito + 1.6.0 + test diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/AbstractDispatcher.java b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/AbstractDispatcher.java index 56f835ed0d..8095abe59b 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/AbstractDispatcher.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/AbstractDispatcher.java @@ -16,6 +16,8 @@ package org.springframework.integration.dispatcher; +import java.util.Collection; +import java.util.Collections; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -26,21 +28,22 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.integration.core.Message; import org.springframework.integration.message.MessageHandler; import org.springframework.integration.message.MessageRejectedException; +import org.springframework.util.Assert; /** * Base class for {@link MessageDispatcher} implementations. * * @author Mark Fisher + * @author Iwein Fuld */ public abstract class AbstractDispatcher implements MessageDispatcher { protected final Log logger = LogFactory.getLog(this.getClass()); - protected final Set handlers = new CopyOnWriteArraySet(); + private final Set handlers = new CopyOnWriteArraySet(); private volatile TaskExecutor taskExecutor; - public boolean addHandler(MessageHandler handler) { return this.handlers.add(handler); } @@ -62,6 +65,10 @@ public abstract class AbstractDispatcher implements MessageDispatcher { return this.taskExecutor; } + protected Set getHandlers() { + return Collections.unmodifiableSet(handlers); + } + public String toString() { return this.getClass().getSimpleName() + " with handlers: " + this.handlers; } @@ -71,16 +78,17 @@ public abstract class AbstractDispatcher implements MessageDispatcher { * "Selective Consumer" throws a {@link MessageRejectedException}. */ protected boolean sendMessageToHandler(Message message, MessageHandler handler) { + Assert.notNull(message, "'message' must not be null"); + Assert.notNull(handler, "'handler' must not be null."); try { handler.handleMessage(message); return true; } catch (MessageRejectedException e) { if (logger.isDebugEnabled()) { - logger.debug("Handler '" + handler + "' rejected Message, continuing with other handlers if available.", e); + logger.debug("Handler '" + handler + "' rejected Message, if other handlers are available this dispatcher may try to send to those.", e); } return false; } } - } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/BroadcastingDispatcher.java b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/BroadcastingDispatcher.java index 4804499a69..0183526fca 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/BroadcastingDispatcher.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/BroadcastingDispatcher.java @@ -31,6 +31,7 @@ import org.springframework.integration.message.MessageHandler; * to the other handlers. * * @author Mark Fisher + * @author Iwein Fuld */ public class BroadcastingDispatcher extends AbstractDispatcher { @@ -48,8 +49,8 @@ public class BroadcastingDispatcher extends AbstractDispatcher { public boolean dispatch(Message message) { int sequenceNumber = 1; - int sequenceSize = this.handlers.size(); - for (final MessageHandler handler : this.handlers) { + int sequenceSize = getHandlers().size(); + for (final MessageHandler handler : getHandlers()) { final Message messageToSend = (!this.applySequence) ? message : MessageBuilder.fromMessage(message) .setSequenceNumber(sequenceNumber++) @@ -70,5 +71,4 @@ public class BroadcastingDispatcher extends AbstractDispatcher { } return true; } - } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/LoadBalancingDispatcher.java b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/LoadBalancingDispatcher.java new file mode 100644 index 0000000000..f3b980100b --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/LoadBalancingDispatcher.java @@ -0,0 +1,67 @@ +/* + * 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.dispatcher; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageDeliveryException; +import org.springframework.integration.message.MessageHandler; +import org.springframework.integration.message.MessageRejectedException; + +/** + * Round-robin implementation of {@link MessageDispatcher} that will attempt to + * send a {@link Message} to one of its handlers. As soon as one of the + * handlers accepts the Message, the dispatcher will return 'true'. This + * implementation will load balance over its handlers using a round-robin + * strategy. + *

+ * If the dispatcher has no handlers, a {@link MessageDeliveryException} will be + * thrown. If all handlers reject the Message, the dispatcher will throw a + * MessageRejectedException. + * + * @author Iwein Fuld + */ +public class LoadBalancingDispatcher extends AbstractDispatcher { + + private final Queue handlerQueue = new ConcurrentLinkedQueue(); + + public boolean dispatch(Message message) { + Set handlers = new HashSet(this.getHandlers()); + if (handlers.isEmpty()) { + throw new MessageDeliveryException(message, "Dispatcher has no subscribers."); + } + if (this.handlerQueue.isEmpty()){ + handlerQueue.addAll(handlers); + } + boolean success = false; + while (!handlerQueue.isEmpty() && success == false) { + MessageHandler handler = handlerQueue.poll(); + if (this.sendMessageToHandler(message, handler)) { + success = true; + } + } + if (!success) { + throw new MessageRejectedException(message, "All of dispatcher's subscribers rejected Message."); + } + return success; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/SimpleDispatcher.java b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/SimpleDispatcher.java index 3b7e4691d5..57ee896762 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/SimpleDispatcher.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/dispatcher/SimpleDispatcher.java @@ -31,16 +31,17 @@ import org.springframework.integration.message.MessageRejectedException; * throw a MessageRejectedException. * * @author Mark Fisher + * @author Iwein Fuld */ public class SimpleDispatcher extends AbstractDispatcher { public boolean dispatch(Message message) { - if (this.handlers.size() == 0) { + if (this.getHandlers().size() == 0) { throw new MessageDeliveryException(message, "Dispatcher has no subscribers."); } int count = 0; int rejectedExceptionCount = 0; - for (MessageHandler handler : this.handlers) { + for (MessageHandler handler : this.getHandlers()) { count++; if (this.sendMessageToHandler(message, handler)) { return true; diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/dispatcher/LoadBalancingDispatcherTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/dispatcher/LoadBalancingDispatcherTests.java new file mode 100644 index 0000000000..525bbae198 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/dispatcher/LoadBalancingDispatcherTests.java @@ -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.dispatcher; + +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnit44Runner; +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageHandler; + +/** + * + * @author Iwein Fuld + * + */ +@RunWith(MockitoJUnit44Runner.class) +public class LoadBalancingDispatcherTests { + + private LoadBalancingDispatcher dispatcher = new LoadBalancingDispatcher(); + + @Mock + private MessageHandler handler; + + @Mock + private Message message; + + @Mock + private MessageHandler differentHandler; + + @Test + public void dispatchMessageWithSingleHandler() throws Exception { + dispatcher.addHandler(handler); + dispatcher.dispatch(message); + } + + @Test + public void differentHandlerInvokedOnSecondMessage() throws Exception { + dispatcher.addHandler(handler); + dispatcher.addHandler(differentHandler); + dispatcher.dispatch(message); + dispatcher.dispatch(message); + verify(handler).handleMessage(message); + verify(differentHandler).handleMessage(message); + } +}