/* * Copyright 2002-2016 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.jms.request_reply; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.Message; import javax.jms.MessageProducer; import javax.jms.TextMessage; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.command.ActiveMQDestination; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Rule; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.gateway.RequestReplyExchanger; import org.springframework.integration.jms.ActiveMQMultiContextTests; import org.springframework.integration.jms.config.ActiveMqTestUtils; import org.springframework.integration.test.support.LongRunningIntegrationTest; import org.springframework.integration.test.util.TestUtils; import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.jms.listener.DefaultMessageListenerContainer; import org.springframework.jms.listener.SessionAwareMessageListener; import org.springframework.jms.support.converter.SimpleMessageConverter; import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.support.GenericMessage; /** * @author Oleg Zhurakousky * @author Gary Russell */ public class RequestReplyScenariosWithTempReplyQueuesTests extends ActiveMQMultiContextTests { private final Log logger = LogFactory.getLog(getClass()); private final SimpleMessageConverter converter = new SimpleMessageConverter(); @Rule public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest(); @SuppressWarnings("resource") @Test public void messageCorrelationBasedOnRequestMessageId() throws Exception { ActiveMqTestUtils.prepare(); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("producer-temp-reply-consumers.xml", this.getClass()); RequestReplyExchanger gateway = context.getBean(RequestReplyExchanger.class); CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); final JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory); final Destination requestDestination = context.getBean("siOutQueue", Destination.class); new Thread(() -> { final Message requestMessage = jmsTemplate.receive(requestDestination); Destination replyTo = null; try { replyTo = requestMessage.getJMSReplyTo(); } catch (Exception e) { fail(); } jmsTemplate.send(replyTo, (MessageCreator) session -> { try { TextMessage message = session.createTextMessage(); message.setText("bar"); message.setJMSCorrelationID(requestMessage.getJMSMessageID()); return message; } catch (Exception e) { // ignore } return null; }); }).start(); gateway.exchange(new GenericMessage<String>("foo")); context.close(); } @Test public void messageCorrelationBasedOnRequestCorrelationIdTimedOutFirstReply() throws Exception { ActiveMqTestUtils.prepare(); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("producer-temp-reply-consumers.xml", this.getClass()); RequestReplyExchanger gateway = context.getBean(RequestReplyExchanger.class); ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); final Destination requestDestination = context.getBean("siOutQueue", Destination.class); DefaultMessageListenerContainer dmlc = new DefaultMessageListenerContainer(); dmlc.setConnectionFactory(connectionFactory); dmlc.setDestination(requestDestination); dmlc.setMessageListener((SessionAwareMessageListener<Message>) (message, session) -> { Destination replyTo = null; try { replyTo = message.getJMSReplyTo(); } catch (Exception e1) { fail(); } String requestPayload = (String) extractPayload(message); if (requestPayload.equals("foo")) { try { Thread.sleep(6000); } catch (Exception e2) { /*ignore*/ } } try { TextMessage replyMessage = session.createTextMessage(); replyMessage.setText(requestPayload); replyMessage.setJMSCorrelationID(message.getJMSMessageID()); MessageProducer producer = session.createProducer(replyTo); producer.send(replyMessage); } catch (Exception e3) { // ignore. the test will fail } }); dmlc.afterPropertiesSet(); dmlc.start(); try { gateway.exchange(new GenericMessage<String>("foo")); } catch (Exception e) { // ignore } Thread.sleep(1000); try { assertEquals("bar", gateway.exchange(new GenericMessage<String>("bar")).getPayload()); } catch (Exception e) { e.printStackTrace(); fail(); } context.close(); } /** * Validates that JOG will recreate a temporary queue * once a failure detected and that the messages will still be properly correlated */ @Test public void brokenBrokerTest() throws Exception { BrokerService broker = new BrokerService(); broker.setPersistent(false); broker.setUseJmx(false); broker.setTransportConnectorURIs(new String[]{"tcp://localhost:61623"}); broker.setDeleteAllMessagesOnStartup(true); broker.start(); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("broken-broker.xml", this.getClass()); final RequestReplyExchanger gateway = context.getBean(RequestReplyExchanger.class); int replyCounter = 0; int timeoutCounter = 0; for (int i = 0; i < 50; i++) { try { assertEquals(i + "", gateway.exchange(new GenericMessage<String>(String.valueOf(i))).getPayload()); replyCounter++; } catch (Exception e) { timeoutCounter++; } if (i == 0 || i == 20 || i == 40) { Object replyDestination = TestUtils.getPropertyValue(context.getBean("jog"), "handler.replyDestination"); if (replyDestination != null) { broker.removeDestination((ActiveMQDestination) replyDestination); } } } assertEquals(50, replyCounter + timeoutCounter); context.close(); } @Test public void testConcurrently() throws Exception { ActiveMqTestUtils.prepare(); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("mult-producer-and-consumers-temp-reply.xml", this.getClass()); final RequestReplyExchanger gateway = context.getBean(RequestReplyExchanger.class); Executor executor = Executors.newFixedThreadPool(10); final int testNumbers = 100; final CountDownLatch latch = new CountDownLatch(testNumbers); final AtomicInteger failures = new AtomicInteger(); final AtomicInteger timeouts = new AtomicInteger(); final AtomicInteger missmatches = new AtomicInteger(); for (int i = 0; i < testNumbers; i++) { final int y = i; executor.execute(() -> { try { String reply = (String) gateway.exchange(new GenericMessage<String>(String.valueOf(y))).getPayload(); if (!String.valueOf(y).equals(reply)) { missmatches.incrementAndGet(); } } catch (Exception e) { if (e instanceof MessageDeliveryException) { timeouts.incrementAndGet(); } else { failures.incrementAndGet(); } } // if (latch.getCount()%100 == 0){ // long count = testNumbers-latch.getCount(); // if (count > 0){ // print(failures, timeouts, missmatches, testNumbers-latch.getCount()); // } // } latch.countDown(); }); } latch.await(); print(failures, timeouts, missmatches, testNumbers); Thread.sleep(5000); assertEquals(0, missmatches.get()); assertEquals(0, failures.get()); assertEquals(0, timeouts.get()); context.close(); } private void print(AtomicInteger failures, AtomicInteger timeouts, AtomicInteger missmatches, long echangesProcessed) { logger.info("============================"); logger.info(echangesProcessed + " exchanges processed"); logger.info("Failures: " + failures.get()); logger.info("Timeouts: " + timeouts.get()); logger.info("Missmatches: " + missmatches.get()); logger.info("============================"); } public static class MyRandomlySlowService { Random random = new Random(); List<Integer> list = new ArrayList<Integer>(); public String secho(String value) throws Exception { int i = random.nextInt(2000); Thread.sleep(i); return value; } } private Object extractPayload(Message jmsMessage) { try { return converter.fromMessage(jmsMessage); } catch (Exception e) { e.printStackTrace(); fail(); } return null; } }