/* * Copyright 2016 Naver Corp. * * 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 com.navercorp.pinpoint.plugin.jdk7.activemq.client; import com.navercorp.pinpoint.bootstrap.plugin.test.ExpectedTrace; import com.navercorp.pinpoint.bootstrap.plugin.test.PluginTestVerifier; import com.navercorp.pinpoint.bootstrap.plugin.test.PluginTestVerifierHolder; import com.navercorp.pinpoint.plugin.jdk7.activemq.client.util.ActiveMQClientITHelper; import com.navercorp.pinpoint.plugin.jdk7.activemq.client.util.AssertTextMessageListener; import com.navercorp.pinpoint.plugin.jdk7.activemq.client.util.MessageConsumerBuilder; import com.navercorp.pinpoint.plugin.jdk7.activemq.client.util.MessageProducerBuilder; import org.apache.activemq.ActiveMQMessageConsumer; import org.apache.activemq.ActiveMQSession; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.command.MessageDispatch; import org.junit.Assert; import org.junit.Test; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.TextMessage; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static com.navercorp.pinpoint.bootstrap.plugin.test.Expectations.annotation; import static com.navercorp.pinpoint.bootstrap.plugin.test.Expectations.event; import static com.navercorp.pinpoint.bootstrap.plugin.test.Expectations.root; /** * @author HyunGil Jeong */ public abstract class ActiveMQClientITBase { public static final String ACTIVEMQ_CLIENT = "ACTIVEMQ_CLIENT"; public static final String ACTIVEMQ_CLIENT_INTERNAL = "ACTIVEMQ_CLIENT_INTERNAL"; protected abstract String getProducerBrokerName(); protected abstract String getProducerBrokerUrl(); protected abstract String getConsumerBrokerName(); protected abstract String getConsumerBrokerUrl(); @Test public void testQueuePull() throws Exception { // Given final String testQueueName = "TestPullQueue"; final ActiveMQQueue testQueue = new ActiveMQQueue(testQueueName); final String testMessage = "Hello World for Queue!"; // create producer ActiveMQSession producerSession = ActiveMQClientITHelper.createSession(getProducerBrokerName(), getProducerBrokerUrl()); MessageProducer producer = producerSession.createProducer(testQueue); final TextMessage expectedTextMessage = producerSession.createTextMessage(testMessage); // When ActiveMQSession consumerSession = ActiveMQClientITHelper.createSession(getConsumerBrokerName(), getConsumerBrokerUrl()); MessageConsumer consumer = consumerSession.createConsumer(testQueue); // Then producer.send(expectedTextMessage); Message message = consumer.receive(1000L); Assert.assertEquals(testMessage, ((TextMessage) message).getText()); // Wait till all traces are recorded (consumer traces are recorded from another thread) awaitAndVerifyTraceCount(5, 5000L); verifyProducerSendEvent(testQueue, producerSession); // trace count : 1 verifyConsumerPullEvent(testQueue, consumerSession, consumer, expectedTextMessage); // trace count : 4 } @Test public void testTopicPull() throws Exception { // Given final String testTopicName = "TestPullTopic"; final ActiveMQTopic testTopic = new ActiveMQTopic(testTopicName); final String testMessage = "Hello World for Topic!"; // create producer ActiveMQSession producerSession = ActiveMQClientITHelper.createSession(getProducerBrokerName(), getProducerBrokerUrl()); MessageProducer producer = new MessageProducerBuilder(producerSession, testTopic).waitTillStarted().build(); final TextMessage expectedTextMessage = producerSession.createTextMessage(testMessage); // create 2 consumers ActiveMQSession consumer1Session = ActiveMQClientITHelper.createSession(getConsumerBrokerName(), getConsumerBrokerUrl()); MessageConsumer consumer1 = new MessageConsumerBuilder(consumer1Session, testTopic).waitTillStarted().build(); ActiveMQSession consumer2Session = ActiveMQClientITHelper.createSession(getConsumerBrokerName(), getConsumerBrokerUrl()); MessageConsumer consumer2 = new MessageConsumerBuilder(consumer2Session, testTopic).waitTillStarted().build(); // When producer.send(expectedTextMessage); Message message1 = consumer1.receive(1000L); Message message2 = consumer2.receive(1000L); Assert.assertEquals(testMessage, ((TextMessage) message1).getText()); Assert.assertEquals(testMessage, ((TextMessage) message2).getText()); // Wait till all traces are recorded (consumer traces are recorded from another thread) awaitAndVerifyTraceCount(9, 5000L); verifyProducerSendEvent(testTopic, producerSession); // trace count : 1 verifyConsumerPullEvent(testTopic, consumer1Session, consumer1, expectedTextMessage); // trace count : 4 verifyConsumerPullEvent(testTopic, consumer2Session, consumer2, expectedTextMessage); // trace count : 4 } @Test public void testQueuePush() throws Exception { // Given final String testQueueName = "TestPushQueue"; final ActiveMQQueue testQueue = new ActiveMQQueue(testQueueName); final String testMessage = "Hello World for Queue!"; final CountDownLatch consumerLatch = new CountDownLatch(1); final Collection<Throwable> consumerThrowables = new CopyOnWriteArrayList<Throwable>(); // create producer ActiveMQSession producerSession = ActiveMQClientITHelper.createSession(getProducerBrokerName(), getProducerBrokerUrl()); MessageProducer producer = producerSession.createProducer(testQueue); final TextMessage expectedTextMessage = producerSession.createTextMessage(testMessage); // create consumer ActiveMQSession consumerSession = ActiveMQClientITHelper.createSession(getConsumerBrokerName(), getConsumerBrokerUrl()); MessageConsumer consumer = consumerSession.createConsumer(testQueue); consumer.setMessageListener(new AssertTextMessageListener(consumerLatch, consumerThrowables, expectedTextMessage)); // When producer.send(expectedTextMessage); consumerLatch.await(1L, TimeUnit.SECONDS); // Then assertNoConsumerError(consumerThrowables); // Wait till all traces are recorded (consumer traces are recorded from another thread) awaitAndVerifyTraceCount(2, 5000L); verifyProducerSendEvent(testQueue, producerSession); // trace count : 1 verifyConsumerPushEvent(testQueue, consumerSession); // trace count : 1 } @Test public void testTopicPush() throws Exception { // Given final String testTopicName = "TestPushTopic"; final ActiveMQTopic testTopic = new ActiveMQTopic(testTopicName); final String testMessage = "Hello World for Topic!"; final int numMessageConsumers = 2; final CountDownLatch consumerConsumeLatch = new CountDownLatch(numMessageConsumers); final Collection<Throwable> consumerThrowables = new CopyOnWriteArrayList<Throwable>(); // create producer ActiveMQSession producerSession = ActiveMQClientITHelper.createSession(getProducerBrokerName(), getProducerBrokerUrl()); MessageProducer producer = new MessageProducerBuilder(producerSession, testTopic).waitTillStarted().build(); final TextMessage expectedTextMessage = producerSession.createTextMessage(testMessage); // create 2 consumers ActiveMQSession consumer1Session = ActiveMQClientITHelper.createSession(getConsumerBrokerName(), getConsumerBrokerUrl()); new MessageConsumerBuilder(consumer1Session, testTopic) .withMessageListener(new AssertTextMessageListener(consumerConsumeLatch, consumerThrowables, expectedTextMessage)) .waitTillStarted() .build(); ActiveMQSession consumer2Session = ActiveMQClientITHelper.createSession(getConsumerBrokerName(), getConsumerBrokerUrl()); new MessageConsumerBuilder(consumer2Session, testTopic) .withMessageListener(new AssertTextMessageListener(consumerConsumeLatch, consumerThrowables, expectedTextMessage)) .waitTillStarted() .build(); // When producer.send(expectedTextMessage); consumerConsumeLatch.await(1L, TimeUnit.SECONDS); // Then // Wait till all traces are recorded (consumer traces are recorded from another thread) awaitAndVerifyTraceCount(3, 1000L); verifyProducerSendEvent(testTopic, producerSession); // trace count : 1 verifyConsumerPushEvent(testTopic, consumer1Session); // trace count : 1 verifyConsumerPushEvent(testTopic, consumer2Session); // trace count : 1 } /** * Verifies traced span event for when {@link org.apache.activemq.ActiveMQMessageProducer ActiveMQMessageProducer} * sends the message. (trace count : 1) * * @param destination the destination to which the producer is sending the message * @throws Exception */ private void verifyProducerSendEvent(ActiveMQDestination destination, ActiveMQSession session) throws Exception { PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); verifier.printCache(); Class<?> messageProducerClass = Class.forName("org.apache.activemq.ActiveMQMessageProducer"); Method send = messageProducerClass.getDeclaredMethod("send", Destination.class, Message.class, int.class, int.class, long.class); // URI producerBrokerUri = new URI(getProducerBrokerUrl()); // String expectedEndPoint = getProducerBrokerUri.getHost() + ":" + producerBrokerUri.getPort(); // String expectedEndPoint = producerBrokerUri.toString(); String expectedEndPoint = session.getConnection().getTransport().getRemoteAddress(); verifier.verifyDiscreteTrace(event( ACTIVEMQ_CLIENT, // serviceType send, // method null, // rpc expectedEndPoint, // endPoint destination.getPhysicalName(), // destinationId annotation("message.queue.url", destination.getQualifiedName()), annotation("activemq.broker.address", expectedEndPoint) )); } /** * Verifies spans and span events for when {@link ActiveMQMessageConsumer} receives the message and enqueues it to * the {@link org.apache.activemq.MessageDispatchChannel MessageDispatchChannel}. The client then invokes any of * {@link ActiveMQMessageConsumer#receive() receive()}, {@link ActiveMQMessageConsumer#receive(long) receive(long)}, * or {@link ActiveMQMessageConsumer#receiveNoWait() receiveNotWait()} to retrieve the message. (trace count : 4) * * @param destination the destination from which the consumer is receiving the message * @param expectedMessage the message the consumer is expected to receive * @throws Exception */ private void verifyConsumerPullEvent(ActiveMQDestination destination, ActiveMQSession session, MessageConsumer consumer, Message expectedMessage) throws Exception { PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); verifier.printCache(); Class<?> messageConsumerClass = Class.forName("org.apache.activemq.ActiveMQMessageConsumer"); Method receiveWithTimeout = messageConsumerClass.getDeclaredMethod("receive", long.class); // URI consumerBrokerUri = new URI(getConsumerBrokerUrl()); // String expectedEndPoint = consumerBrokerUri.toString(); String expectedEndPoint = session.getConnection().getTransport().getRemoteAddress(); ExpectedTrace consumerDispatchTrace = root(ACTIVEMQ_CLIENT, // serviceType "ActiveMQ Consumer Invocation", // method destination.getQualifiedName(), // rpc null, // endPoint (collected but there's no easy way to retrieve local address) expectedEndPoint); ExpectedTrace consumerReceiveTrace = event(ACTIVEMQ_CLIENT_INTERNAL, // serviceType receiveWithTimeout, // method annotation("activemq.message", getMessageAsString(expectedMessage))); Class<?> messageDispatchChannel = getMessageDispatchChannelClass(consumer); if (messageDispatchChannel != null) { Method enqueue = messageDispatchChannel.getDeclaredMethod("enqueue", MessageDispatch.class); Method dequeueWithTimeout = messageDispatchChannel.getDeclaredMethod("dequeue", long.class); // Consumer dispatches and enqueues the message to dispatch channel automatically verifier.verifyDiscreteTrace(consumerDispatchTrace, event(ACTIVEMQ_CLIENT_INTERNAL, enqueue)); // Client receives the message by dequeueing it from the dispatch channel verifier.verifyDiscreteTrace(consumerReceiveTrace, event(ACTIVEMQ_CLIENT_INTERNAL, dequeueWithTimeout)); } else { // Consumer dispatches and enqueues the message to dispatch channel automatically verifier.verifyDiscreteTrace(consumerDispatchTrace); // Client receives the message by dequeueing it from the dispatch channel verifier.verifyDiscreteTrace(consumerReceiveTrace); } } /** * Verifies spans and span events for when {@link ActiveMQMessageConsumer} receives the message and invokes it's * {@link javax.jms.MessageListener MessageListener}. (trace count : 1) * * @param destination the destination from which the consumer is receiving the message * @throws Exception */ private void verifyConsumerPushEvent(ActiveMQDestination destination, ActiveMQSession session) throws Exception { PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); // URI consumerBrokerUri = new URI(getConsumerBrokerUrl()); // String expectedRemoteAddress = consumerBrokerUri.toString(); String expectedRemoteAddress = session.getConnection().getTransport().getRemoteAddress(); verifier.verifyDiscreteTrace(root( ACTIVEMQ_CLIENT, // serviceType "ActiveMQ Consumer Invocation", // method destination.getQualifiedName(), // rpc null, // endPoint (collected but there's no easy way to retrieve local address so skip check) expectedRemoteAddress // remoteAddress )); } private Class<?> getMessageDispatchChannelClass(MessageConsumer consumer) throws NoSuchFieldException, IllegalAccessException { final String messageDispatchChannelFieldName = "unconsumedMessages"; Class<?> consumerClass = consumer.getClass(); // Need a better way as field names could change in future versions. Comparing classes or class names doesn't // work due to class loading issue, and some versions may not have certain implementations of // MessageDispatchChannel. // Test should be fixed if anything changes in future ActiveMQClient library Field messageDispatchChannelField = consumerClass.getDeclaredField(messageDispatchChannelFieldName); messageDispatchChannelField.setAccessible(true); return messageDispatchChannelField.get(consumer).getClass(); } private String getMessageAsString(Message message) throws JMSException { StringBuilder messageStringBuilder = new StringBuilder(message.getClass().getSimpleName()); if (message instanceof TextMessage) { messageStringBuilder.append('{').append(((TextMessage) message).getText()).append('}'); } return messageStringBuilder.toString(); } protected final void assertNoConsumerError(Collection<Throwable> consumerThrowables) { Assert.assertTrue("Consumer Error : " + consumerThrowables.toString(), consumerThrowables.isEmpty()); } protected final void awaitAndVerifyTraceCount(int expectedTraceCount, long maxWaitMs) throws InterruptedException { PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); final long waitIntervalMs = 100L; long maxWaitTime = maxWaitMs; if (maxWaitMs < waitIntervalMs) { maxWaitTime = waitIntervalMs; } long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < maxWaitTime) { try { verifier.verifyTraceCount(expectedTraceCount); return; } catch (AssertionError e) { // ignore and retry Thread.sleep(waitIntervalMs); } } verifier.printCache(); verifier.verifyTraceCount(expectedTraceCount); } }