From a07f0313002a0bfb28f7f7a94cb74e7648d6fabb Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 20 Feb 2008 15:30:41 +0000 Subject: [PATCH] Added initial version of RequestReplyTemplate (INT-86). --- .../channel/RequestReplyTemplate.java | 150 ++++++++++++++++++ .../integration/channel/SimpleChannel.java | 4 +- .../channel/RequestReplyTemplateTests.java | 86 ++++++++++ 3 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/channel/RequestReplyTemplate.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/channel/RequestReplyTemplateTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/RequestReplyTemplate.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/RequestReplyTemplate.java new file mode 100644 index 0000000000..8ec4ee0dd6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/RequestReplyTemplate.java @@ -0,0 +1,150 @@ +/* + * 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.channel; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.springframework.integration.handler.ReplyHandler; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHeader; +import org.springframework.util.Assert; + +/** + * A template that facilitates the implementation of request-reply usage + * scenarios above one-way {@link MessageChannel MessageChannels}. + * + * @author Mark Fisher + */ +public class RequestReplyTemplate { + + private final MessageChannel requestChannel; + + private final ExecutorService executor; + + private volatile long defaultSendTimeout = -1; + + private volatile long defaultReceiveTimeout = -1; + + + /** + * Create a RequestReplyTemplate. + * + * @param requestChannel the channel to which request messages will be sent + * @param executor the executor that will handle asynchronous (non-blocking) + * requests + */ + public RequestReplyTemplate(MessageChannel requestChannel, ExecutorService executor) { + Assert.notNull(requestChannel, "'requestChannel' must not be null"); + Assert.notNull(executor, "'executor' must not be null"); + this.requestChannel = requestChannel; + this.executor = executor; + } + + /** + * Create a RequestReplyTemplate with a default single-threaded executor. + * + * @param requestChannel the channel to which request messages will be sent + */ + public RequestReplyTemplate(MessageChannel requestChannel) { + this(requestChannel, Executors.newSingleThreadExecutor()); + } + + + /** + * Set the default timeout value for sending request messages. If not + * explicitly configured, the default will be an indefinite timeout. + * + * @param defaultSendTimeout the timeout value in milliseconds + */ + public void setDefaultSendTimeout(long defaultSendTimeout) { + this.defaultSendTimeout = defaultSendTimeout; + } + + /** + * Set the default timeout value for receiving reply messages. If not + * explicitly configured, the default will be an indefinite timeout. + * + * @param defaultReceiveTimeout the timeout value in milliseconds + */ + public void setDefaultReceiveTimeout(long defaultReceiveTimeout) { + this.defaultReceiveTimeout = defaultReceiveTimeout; + } + + /** + * Send a request message and wait for a reply message using the provided + * timeout values. + * + * @param requestMessage the request message to send + * @param sendTimeout the timeout value for sending the request message + * @param receiveTimeout the timeout value for receiving a reply message + * + * @return the reply message or null + */ + public Message request(Message requestMessage, long sendTimeout, long receiveTimeout) { + SimpleChannel replyChannel = new SimpleChannel(0); + requestMessage.getHeader().setReturnAddress(replyChannel); + this.requestChannel.send(requestMessage, sendTimeout); + return replyChannel.receive(receiveTimeout); + } + + /** + * Send a request message and wait for a reply message using the default + * timeout values. + * + * @param requestMessage the request message to send + * + * @return the reply message or null + */ + public Message request(Message requestMessage) { + return this.request(requestMessage, this.defaultSendTimeout, this.defaultReceiveTimeout); + } + + /** + * Send a request message asynchronously to be handled by the provided + * {@link ReplyHandler}. The provided values will be used for the send + * and receive timeouts. + * + * @param requestMessage the request message to send + * @param replyHandler the callback that will handle a reply message + * @param sendTimeout the timeout value for sending the request message + * @param receiveTimeout the timeout value for receiving the reply message + */ + public void request(final Message requestMessage, final ReplyHandler replyHandler, final long sendTimeout, + final long receiveTimeout) { + final MessageHeader header = requestMessage.getHeader(); + this.executor.submit(new Runnable() { + public void run() { + Message reply = request(requestMessage, sendTimeout, receiveTimeout); + replyHandler.handle(reply, header); + } + }); + } + + /** + * Send a request message asynchronously to be handled by the provided + * {@link ReplyHandler}. Default values will be used for the send and + * receive timeouts. + * + * @param requestMessage the request message to send + * @param replyHandler the callback that will handle a reply message + */ + public void request(final Message requestMessage, final ReplyHandler replyHandler) { + this.request(requestMessage, replyHandler, this.defaultSendTimeout, this.defaultReceiveTimeout); + } + +} 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 9cc0bddbc1..3c0db258c6 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 @@ -77,7 +77,7 @@ public class SimpleChannel extends AbstractMessageChannel { } - public boolean doSend(Message message, long timeout) { + protected boolean doSend(Message message, long timeout) { Assert.notNull(message, "'message' must not be null"); try { if (timeout > 0) { @@ -95,7 +95,7 @@ public class SimpleChannel extends AbstractMessageChannel { } } - public Message doReceive(long timeout) { + protected Message doReceive(long timeout) { try { if (timeout > 0) { return queue.poll(timeout, TimeUnit.MILLISECONDS); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/RequestReplyTemplateTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/RequestReplyTemplateTests.java new file mode 100644 index 0000000000..45197d375f --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/RequestReplyTemplateTests.java @@ -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.channel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.springframework.integration.bus.MessageBus; +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.ReplyHandler; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHeader; +import org.springframework.integration.message.StringMessage; +import org.springframework.integration.scheduling.Subscription; + +/** + * @author Mark Fisher + */ +public class RequestReplyTemplateTests { + + private final SimpleChannel requestChannel = new SimpleChannel(); + + + public RequestReplyTemplateTests() { + MessageHandler testHandler = new MessageHandler() { + public Message handle(Message message) { + return new StringMessage(message.getPayload().toString().toUpperCase()); + } + }; + MessageBus bus = new MessageBus(); + bus.registerChannel("requestChannel", requestChannel); + bus.registerHandler("testHandler", testHandler, new Subscription(requestChannel)); + bus.start(); + } + + + @Test + public void testSynchronousRequestReply() { + RequestReplyTemplate template = new RequestReplyTemplate(requestChannel); + Message reply = template.request(new StringMessage("test")); + assertEquals("TEST", reply.getPayload()); + } + + @Test + public void testAsynchronousRequestAndReply() throws InterruptedException { + final List replies = new ArrayList(3); + final CountDownLatch latch = new CountDownLatch(3); + ReplyHandler replyHandler = new ReplyHandler() { + public void handle(Message replyMessage, MessageHeader originalMessageHeader) { + replies.add((String) replyMessage.getPayload()); + latch.countDown(); + } + }; + RequestReplyTemplate template = new RequestReplyTemplate(requestChannel); + template.request(new StringMessage("test1"), replyHandler); + template.request(new StringMessage("test2"), replyHandler); + template.request(new StringMessage("test3"), replyHandler); + latch.await(1000, TimeUnit.MILLISECONDS); + assertEquals(0, latch.getCount()); + assertTrue(replies.contains("TEST1")); + assertTrue(replies.contains("TEST2")); + assertTrue(replies.contains("TEST3")); + } + +}