/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.camel.component.jms; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.ConnectionFactory; import org.apache.activemq.camel.component.ActiveMQComponent; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.ExchangePattern; import org.apache.camel.ExchangeTimedOutException; import org.apache.camel.Message; import org.apache.camel.Processor; import org.apache.camel.RuntimeCamelException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.test.junit4.CamelTestSupport; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import static org.apache.camel.component.jms.JmsComponent.jmsComponentAutoAcknowledge; /** * @version */ public class JmsRouteRequestReplyTest extends CamelTestSupport { protected static final String REPLY_TO_DESTINATION_SELECTOR_NAME = "camelProducer"; protected static String componentName = "amq"; protected static String componentName1 = "amq1"; protected static String endpointUriA = componentName + ":queue:test.a"; protected static String endpointUriB = componentName + ":queue:test.b"; protected static String endpointUriB1 = componentName1 + ":queue:test.b"; // note that the replyTo both A and B endpoints share the persistent replyTo queue, // which is one more way to verify that reply listeners of A and B endpoints don't steal each other messages protected static String endpointReplyToUriA = componentName + ":queue:test.a?replyTo=queue:test.a.reply"; protected static String endpointReplyToUriB = componentName + ":queue:test.b?replyTo=queue:test.a.reply"; protected static String request = "Hello World"; protected static String expectedReply = "Re: " + request; protected static int maxTasks = 20; protected static int maxServerTasks = 1; protected static int maxCalls = 5; protected static AtomicBoolean inited = new AtomicBoolean(false); protected static Map<String, ContextBuilder> contextBuilders = new HashMap<String, ContextBuilder>(); protected static Map<String, RouteBuilder> routeBuilders = new HashMap<String, RouteBuilder>(); private interface ContextBuilder { CamelContext buildContext(CamelContext context) throws Exception; } public static class SingleNodeDeadEndRouteBuilder extends RouteBuilder { public void configure() throws Exception { from(endpointUriA) // We are not expect the response here .setExchangePattern(ExchangePattern.InOnly).process(new Processor() { public void process(Exchange e) { // do nothing } }); } }; public static class SingleNodeRouteBuilder extends RouteBuilder { public void configure() throws Exception { from(endpointUriA).process(new Processor() { public void process(Exchange e) { String request = e.getIn().getBody(String.class); e.getOut().setBody(expectedReply + request.substring(request.indexOf('-'))); } }); } }; public static class MultiNodeRouteBuilder extends RouteBuilder { public void configure() throws Exception { from(endpointUriA).to(endpointUriB); from(endpointUriB).process(new Processor() { public void process(Exchange e) { String request = e.getIn().getBody(String.class); e.getOut().setBody(expectedReply + request.substring(request.indexOf('-'))); } }); } }; public static class MultiNodeReplyToRouteBuilder extends RouteBuilder { public void configure() throws Exception { from(endpointUriA).to(endpointReplyToUriB); from(endpointUriB).process(new Processor() { public void process(Exchange e) { Message in = e.getIn(); Message out = e.getOut(); String selectorValue = in.getHeader(REPLY_TO_DESTINATION_SELECTOR_NAME, String.class); String request = in.getBody(String.class); out.setHeader(REPLY_TO_DESTINATION_SELECTOR_NAME, selectorValue); out.setBody(expectedReply + request.substring(request.indexOf('-'))); } }); } }; public static class MultiNodeDiffCompRouteBuilder extends RouteBuilder { public void configure() throws Exception { from(endpointUriA).to(endpointUriB1); from(endpointUriB1).process(new Processor() { public void process(Exchange e) { String request = e.getIn().getBody(String.class); e.getOut().setBody(expectedReply + request.substring(request.indexOf('-'))); } }); } }; public static class ContextBuilderMessageID implements ContextBuilder { public CamelContext buildContext(CamelContext context) throws Exception { ConnectionFactory connectionFactory = CamelJmsTestHelper.createConnectionFactory(); JmsComponent jmsComponent = jmsComponentAutoAcknowledge(connectionFactory); jmsComponent.setUseMessageIDAsCorrelationID(true); jmsComponent.setConcurrentConsumers(maxServerTasks); context.addComponent(componentName, jmsComponent); return context; } }; protected static void init() { if (inited.compareAndSet(false, true)) { ContextBuilder contextBuilderMessageID = new ContextBuilderMessageID(); ContextBuilder contextBuilderCorrelationID = new ContextBuilder() { public CamelContext buildContext(CamelContext context) throws Exception { ConnectionFactory connectionFactory = CamelJmsTestHelper.createConnectionFactory(); ActiveMQComponent jmsComponent = ActiveMQComponent.activeMQComponent(); jmsComponent.setConnectionFactory(connectionFactory); jmsComponent.setUseMessageIDAsCorrelationID(false); jmsComponent.setConcurrentConsumers(maxServerTasks); context.addComponent(componentName, jmsComponent); return context; } }; ContextBuilder contextBuilderMessageIDNamedReplyToSelector = new ContextBuilder() { public CamelContext buildContext(CamelContext context) throws Exception { ConnectionFactory connectionFactory = CamelJmsTestHelper.createConnectionFactory(); ActiveMQComponent jmsComponent = ActiveMQComponent.activeMQComponent(); jmsComponent.setConnectionFactory(connectionFactory); jmsComponent.setUseMessageIDAsCorrelationID(true); jmsComponent.setConcurrentConsumers(maxServerTasks); jmsComponent.getConfiguration().setReplyToDestinationSelectorName(REPLY_TO_DESTINATION_SELECTOR_NAME); context.addComponent(componentName, jmsComponent); return context; } }; ContextBuilder contextBuilderCorrelationIDNamedReplyToSelector = new ContextBuilder() { public CamelContext buildContext(CamelContext context) throws Exception { ConnectionFactory connectionFactory = CamelJmsTestHelper.createConnectionFactory(); ActiveMQComponent jmsComponent = ActiveMQComponent.activeMQComponent(); jmsComponent.setConnectionFactory(connectionFactory); jmsComponent.setUseMessageIDAsCorrelationID(false); jmsComponent.setConcurrentConsumers(maxServerTasks); jmsComponent.getConfiguration().setReplyToDestinationSelectorName(REPLY_TO_DESTINATION_SELECTOR_NAME); context.addComponent(componentName, jmsComponent); return context; } }; ContextBuilder contextBuilderCorrelationIDDiffComp = new ContextBuilder() { public CamelContext buildContext(CamelContext context) throws Exception { ConnectionFactory connectionFactory = CamelJmsTestHelper.createConnectionFactory(); ActiveMQComponent jmsComponent = ActiveMQComponent.activeMQComponent(); jmsComponent.setConnectionFactory(connectionFactory); jmsComponent.setConcurrentConsumers(maxServerTasks); context.addComponent(componentName, jmsComponent); ActiveMQComponent jmsComponent1 = ActiveMQComponent.activeMQComponent(); jmsComponent1.setConnectionFactory(connectionFactory); jmsComponent1.setUseMessageIDAsCorrelationID(false); jmsComponent1.setConcurrentConsumers(maxServerTasks); context.addComponent(componentName1, jmsComponent1); return context; } }; ContextBuilder contextBuilderMessageIDDiffComp = new ContextBuilder() { public CamelContext buildContext(CamelContext context) throws Exception { ConnectionFactory connectionFactory = CamelJmsTestHelper.createConnectionFactory(); ActiveMQComponent jmsComponent = ActiveMQComponent.activeMQComponent(); jmsComponent.setConnectionFactory(connectionFactory); jmsComponent.setUseMessageIDAsCorrelationID(true); jmsComponent.setConcurrentConsumers(maxServerTasks); context.addComponent(componentName, jmsComponent); ActiveMQComponent jmsComponent1 = ActiveMQComponent.activeMQComponent(); jmsComponent1.setConnectionFactory(connectionFactory); jmsComponent1.setUseMessageIDAsCorrelationID(true); jmsComponent1.setConcurrentConsumers(maxServerTasks); context.addComponent(componentName1, jmsComponent1); return context; } }; contextBuilders.put("testUseMessageIDAsCorrelationID", contextBuilderMessageID); contextBuilders.put("testUseCorrelationID", contextBuilderCorrelationID); contextBuilders.put("testUseMessageIDAsCorrelationIDMultiNode", contextBuilderMessageID); contextBuilders.put("testUseCorrelationIDMultiNode", contextBuilderCorrelationID); contextBuilders.put("testUseMessageIDAsCorrelationIDPersistReplyToMultiNode", contextBuilderMessageID); contextBuilders.put("testUseCorrelationIDPersistReplyToMultiNode", contextBuilderCorrelationID); contextBuilders.put("testUseMessageIDAsCorrelationIDPersistMultiReplyToMultiNode", contextBuilderMessageID); // contextBuilders.put("testUseCorrelationIDPersistMultiReplyToMultiNode", contextBuilderCorrelationID); contextBuilders.put("testUseMessageIDAsCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode", contextBuilderMessageIDNamedReplyToSelector); contextBuilders.put("testUseCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode", contextBuilderCorrelationIDNamedReplyToSelector); contextBuilders.put("testUseCorrelationIDMultiNodeDiffComponents", contextBuilderCorrelationIDDiffComp); contextBuilders.put("testUseMessageIDAsCorrelationIDMultiNodeDiffComponents", contextBuilderMessageIDDiffComp); contextBuilders.put("testUseMessageIDAsCorrelationIDTimeout", contextBuilderMessageID); contextBuilders.put("testUseCorrelationIDTimeout", contextBuilderMessageID); routeBuilders.put("testUseMessageIDAsCorrelationID", new SingleNodeRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDReplyToTempDestinationPerComponent", new SingleNodeRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDReplyToTempDestinationPerProducer", new SingleNodeRouteBuilder()); routeBuilders.put("testUseCorrelationID", new SingleNodeRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDMultiNode", new MultiNodeRouteBuilder()); routeBuilders.put("testUseCorrelationIDMultiNode", new MultiNodeRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDPersistReplyToMultiNode", new MultiNodeRouteBuilder()); routeBuilders.put("testUseCorrelationIDPersistReplyToMultiNode", new MultiNodeRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDPersistMultiReplyToMultiNode", new MultiNodeReplyToRouteBuilder()); // routeBuilders.put("testUseCorrelationIDPersistMultiReplyToMultiNode", new MultiNodeReplyToRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode", new MultiNodeReplyToRouteBuilder()); routeBuilders.put("testUseCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode", new MultiNodeReplyToRouteBuilder()); routeBuilders.put("testUseCorrelationIDMultiNodeDiffComponents", new MultiNodeDiffCompRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDMultiNodeDiffComponents", new MultiNodeDiffCompRouteBuilder()); routeBuilders.put("testUseMessageIDAsCorrelationIDTimeout", new SingleNodeDeadEndRouteBuilder()); routeBuilders.put("testUseCorrelationIDTimeout", new SingleNodeDeadEndRouteBuilder()); } } public class Task implements Callable<Task> { private AtomicInteger counter; private String fromUri; private volatile boolean ok = true; private volatile String message = ""; public Task(AtomicInteger counter, String fromUri) { this.counter = counter; this.fromUri = fromUri; } public Task call() throws Exception { for (int i = 0; i < maxCalls; i++) { int callId = counter.incrementAndGet(); Object reply = ""; try { reply = template.requestBody(fromUri, request + "-" + callId); } catch (RuntimeCamelException e) { // expected in some cases } if (!reply.equals(expectedReply + "-" + callId)) { ok = false; message = "Unexpected reply. Expected: '" + expectedReply + "-" + callId + "'; Received: '" + reply + "'"; } } return this; } public void assertSuccess() { assertTrue(message, ok); } } @Before public void setUp() throws Exception { init(); super.setUp(); } @Test public void testUseMessageIDAsCorrelationID() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseCorrelationID() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseMessageIDAsCorrelationIDMultiNode() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseCorrelationIDMultiNode() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseMessageIDAsCorrelationIDPersistReplyToMultiNode() throws Exception { runRequestReplyThreaded(endpointReplyToUriA); } @Test public void testUseCorrelationIDPersistReplyToMultiNode() throws Exception { runRequestReplyThreaded(endpointUriA); } // (1) // note this is an inefficient way of correlating replies to a persistent queue // a consumer will have to be created for each reply message // see testUseMessageIDAsCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode // or testCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode // for a faster way to do this. Note however that in this case the message copy has to occur // between consumer -> producer as the selector value needs to be propagated to the ultimate // destination, which in turn will copy this value back into the reply message @Test public void testUseMessageIDAsCorrelationIDPersistMultiReplyToMultiNode() throws Exception { int oldMaxTasks = maxTasks; int oldMaxServerTasks = maxServerTasks; int oldMaxCalls = maxCalls; maxTasks = 10; maxServerTasks = 1; maxCalls = 2; try { runRequestReplyThreaded(endpointUriA); } finally { maxTasks = oldMaxTasks; maxServerTasks = oldMaxServerTasks; maxCalls = oldMaxCalls; } } // see (1) @Test @Ignore public void testUseCorrelationIDPersistMultiReplyToMultiNode() throws Exception { int oldMaxTasks = maxTasks; int oldMaxServerTasks = maxServerTasks; int oldMaxCalls = maxCalls; maxTasks = 10; maxServerTasks = 1; maxCalls = 2; try { runRequestReplyThreaded(endpointUriA); } finally { maxTasks = oldMaxTasks; maxServerTasks = oldMaxServerTasks; maxCalls = oldMaxCalls; } } @Test public void testUseMessageIDAsCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseCorrelationIDPersistMultiReplyToWithNamedSelectorMultiNode() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseCorrelationIDTimeout() throws Exception { JmsComponent c = (JmsComponent)context.getComponent(componentName); c.getConfiguration().setRequestTimeout(1000); Object reply = ""; try { reply = template.requestBody(endpointUriA, request); fail("Should have thrown exception"); } catch (RuntimeCamelException e) { assertIsInstanceOf(ExchangeTimedOutException.class, e.getCause()); } assertEquals("", reply); } @Test public void testUseMessageIDAsCorrelationIDTimeout() throws Exception { JmsComponent c = (JmsComponent)context.getComponent(componentName); c.getConfiguration().setRequestTimeout(1000); Object reply = ""; try { reply = template.requestBody(endpointUriA, request); fail("Should have thrown exception"); } catch (RuntimeCamelException e) { assertIsInstanceOf(ExchangeTimedOutException.class, e.getCause()); } assertEquals("", reply); } @Test public void testUseCorrelationIDMultiNodeDiffComponents() throws Exception { runRequestReplyThreaded(endpointUriA); } @Test public void testUseMessageIDAsCorrelationIDMultiNodeDiffComponents() throws Exception { runRequestReplyThreaded(endpointUriA); } protected void runRequestReplyThreaded(String fromUri) throws Exception { // start template template.start(); ExecutorService executor = context.getExecutorServiceManager().newFixedThreadPool(this, "Task", maxTasks); CompletionService<Task> completionService = new ExecutorCompletionService<Task>(executor); final AtomicInteger counter = new AtomicInteger(-1); for (int i = 0; i < maxTasks; i++) { Task task = new Task(counter, fromUri); completionService.submit(task); } for (int i = 0; i < maxTasks; i++) { Future<Task> future = completionService.take(); Task task = future.get(60, TimeUnit.SECONDS); assertNotNull("Should complete the task", task); task.assertSuccess(); } context.getExecutorServiceManager().shutdownNow(executor); } protected CamelContext createCamelContext() throws Exception { CamelContext camelContext = super.createCamelContext(); return contextBuilders.get(getTestMethodName()).buildContext(camelContext); } protected RouteBuilder createRouteBuilder() throws Exception { return routeBuilders.get(getTestMethodName()); } }