From 4d0d0f6e42cfb4b5be59685d8730e96dccdad2d9 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 19 May 2026 11:30:20 +0200 Subject: [PATCH 1/4] Have a fully JMS 1.0 compatible instrumentation --- .../instrumentation/jms/JMSDecorator.java | 15 ++ .../JMSMessageProducerInstrumentation.java | 4 +- .../jms/SessionInstrumentation.java | 4 +- .../src/test/groovy/JMS1Test.groovy | 33 ++- .../test/java/jms10mock/Jms10Connection.java | 130 ++++++++++ .../jms10mock/Jms10ConnectionFactory.java | 61 +++++ .../java/jms10mock/Jms10QueueReceiver.java | 59 +++++ .../test/java/jms10mock/Jms10QueueSender.java | 124 ++++++++++ .../src/test/java/jms10mock/Jms10Session.java | 229 ++++++++++++++++++ .../java/jms10mock/Jms10TopicPublisher.java | 137 +++++++++++ .../java/jms10mock/Jms10TopicSubscriber.java | 66 +++++ 11 files changed, 847 insertions(+), 15 deletions(-) create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Connection.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10ConnectionFactory.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueReceiver.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueSender.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Session.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicPublisher.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicSubscriber.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java index 87fbfc55fc7..c9dd753b3da 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java @@ -19,10 +19,13 @@ import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; +import javax.jms.MessageProducer; import javax.jms.Queue; +import javax.jms.QueueSender; import javax.jms.TemporaryQueue; import javax.jms.TemporaryTopic; import javax.jms.Topic; +import javax.jms.TopicPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -265,6 +268,18 @@ public CharSequence toResourceName(String destinationName, boolean isQueue) { return joiner.apply(destinationName); } + public Destination getDestination(final MessageProducer messageProducer) throws JMSException { + try { + return messageProducer.getDestination(); // >= 1.1 + } catch (AbstractMethodError ignored) { + // <=1.1 getDestination is not available so we need to pay an additional instanceOf + if (messageProducer instanceof QueueSender) { + return ((QueueSender) messageProducer).getQueue(); + } + return ((TopicPublisher) messageProducer).getTopic(); + } + } + public String getDestinationName(Destination destination) { String name = null; try { diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java index 972b4382209..3dbfa0579f5 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java @@ -90,10 +90,10 @@ public static AgentScope beforeSend( // fall-back when producer wasn't created via standard Session.createProducer API if (null != producerState) { resourceName = producerState.getResourceName(); - Destination destination = producer.getDestination(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); destinationName = PRODUCER_DECORATE.getDestinationName(destination); } else { - Destination destination = producer.getDestination(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); destinationName = PRODUCER_DECORATE.getDestinationName(destination); boolean isQueue = PRODUCER_DECORATE.isQueue(destination); resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java index 3f7b095b3b7..8c3ffa48231 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java @@ -114,7 +114,7 @@ public static void bindProducerState( int ackMode; try { ackMode = session.getAcknowledgeMode(); - } catch (Exception ignored) { + } catch (Throwable ignored) { ackMode = Session.AUTO_ACKNOWLEDGE; } sessionState = @@ -155,7 +155,7 @@ public static void bindConsumerState( int ackMode; try { ackMode = session.getAcknowledgeMode(); - } catch (Exception ignored) { + } catch (Throwable ignored) { ackMode = Session.AUTO_ACKNOWLEDGE; } sessionState = diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy index f059016bc62..f24b7e8f9ff 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy @@ -7,32 +7,32 @@ import datadog.trace.agent.test.naming.VersionedNamingTestBase import datadog.trace.api.Config import datadog.trace.api.DDSpanTypes import datadog.trace.api.Trace -import datadog.trace.api.config.TracerConfig import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.api.config.TracerConfig import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.core.DDSpan -import org.apache.activemq.ActiveMQConnectionFactory -import org.apache.activemq.command.ActiveMQTextMessage -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import spock.lang.Shared - +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference import javax.jms.Connection +import javax.jms.ConnectionFactory import javax.jms.Destination import javax.jms.Message import javax.jms.MessageListener +import javax.jms.Queue import javax.jms.QueueConnection import javax.jms.QueueSession import javax.jms.Session import javax.jms.TemporaryQueue import javax.jms.TemporaryTopic -import javax.jms.Queue -import javax.jms.Topic import javax.jms.TextMessage +import javax.jms.Topic import javax.jms.TopicConnection import javax.jms.TopicSession -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference +import jms10mock.Jms10ConnectionFactory +import org.apache.activemq.command.ActiveMQTextMessage +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import spock.lang.Shared abstract class JMS1Test extends VersionedNamingTestBase { @Shared @@ -69,9 +69,13 @@ abstract class JMS1Test extends VersionedNamingTestBase { true } + def createConnectionFactory() { + broker.createConnectionFactory() + } + def setupSpec() { broker.start() - final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() + final ConnectionFactory connectionFactory = createConnectionFactory() connection = connectionFactory.createConnection() connection.start() @@ -1097,3 +1101,10 @@ class JMS1V1ForkedTest extends JMS1Test { "jms.process" } } + +class JMS10Test extends JMS1V0Test { + @Override + def createConnectionFactory() { + new Jms10ConnectionFactory(super.createConnectionFactory()) + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Connection.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Connection.java new file mode 100644 index 00000000000..0f8721a2b89 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Connection.java @@ -0,0 +1,130 @@ +package jms10mock; + +import javax.jms.Connection; +import javax.jms.ConnectionConsumer; +import javax.jms.ConnectionMetaData; +import javax.jms.Destination; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.jms.Queue; +import javax.jms.QueueConnection; +import javax.jms.QueueSession; +import javax.jms.ServerSessionPool; +import javax.jms.Session; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicSession; + +/** Wraps a real {@link Connection} but simulates a JMS 1.0 provider. */ +public class Jms10Connection implements QueueConnection, TopicConnection { + private final Connection delegate; + + public Jms10Connection(Connection delegate) { + this.delegate = delegate; + } + + // --- JMS 1.1-only unified Connection method --- + + @Override + public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { + throw new AbstractMethodError( + "JMS 1.0 provider does not implement createSession(boolean, int) on Connection"); + } + + // --- JMS 1.0 QueueConnection methods --- + + @Override + public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) + throws JMSException { + return new Jms10Session(delegate.createSession(transacted, acknowledgeMode)); + } + + // --- JMS 1.0 TopicConnection methods --- + + @Override + public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) + throws JMSException { + return new Jms10Session(delegate.createSession(transacted, acknowledgeMode)); + } + + // --- Common Connection methods --- + + @Override + public String getClientID() throws JMSException { + return delegate.getClientID(); + } + + @Override + public void setClientID(String clientID) throws JMSException { + delegate.setClientID(clientID); + } + + @Override + public ConnectionMetaData getMetaData() throws JMSException { + return delegate.getMetaData(); + } + + @Override + public ExceptionListener getExceptionListener() throws JMSException { + return delegate.getExceptionListener(); + } + + @Override + public void setExceptionListener(ExceptionListener listener) throws JMSException { + delegate.setExceptionListener(listener); + } + + @Override + public void start() throws JMSException { + delegate.start(); + } + + @Override + public void stop() throws JMSException { + delegate.stop(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } + + // --- ConnectionConsumer methods — not commonly used, throw for JMS 1.1 unified form --- + + @Override + public ConnectionConsumer createConnectionConsumer( + Destination destination, + String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) + throws JMSException { + throw new AbstractMethodError( + "JMS 1.0 provider does not implement createConnectionConsumer(Destination, ...)"); + } + + @Override + public ConnectionConsumer createConnectionConsumer( + Queue queue, String messageSelector, ServerSessionPool sessionPool, int maxMessages) + throws JMSException { + return delegate.createConnectionConsumer(queue, messageSelector, sessionPool, maxMessages); + } + + @Override + public ConnectionConsumer createConnectionConsumer( + Topic topic, String messageSelector, ServerSessionPool sessionPool, int maxMessages) + throws JMSException { + return delegate.createConnectionConsumer(topic, messageSelector, sessionPool, maxMessages); + } + + @Override + public ConnectionConsumer createDurableConnectionConsumer( + Topic topic, + String subscriptionName, + String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) + throws JMSException { + return delegate.createDurableConnectionConsumer( + topic, subscriptionName, messageSelector, sessionPool, maxMessages); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10ConnectionFactory.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10ConnectionFactory.java new file mode 100644 index 00000000000..1660765f731 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10ConnectionFactory.java @@ -0,0 +1,61 @@ +package jms10mock; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.QueueConnection; +import javax.jms.QueueConnectionFactory; +import javax.jms.TopicConnection; +import javax.jms.TopicConnectionFactory; + +/** + * Wraps a real {@link ConnectionFactory} but simulates a JMS 1.0 provider. + * + *

In JMS 1.0, clients used the domain-specific {@link QueueConnectionFactory} and {@link + * TopicConnectionFactory} to obtain connections. The unified {@link ConnectionFactory} and its + * {@code createConnection()} methods are JMS 1.1 additions that this wrapper does not support. + */ +public class Jms10ConnectionFactory implements QueueConnectionFactory, TopicConnectionFactory { + private final ConnectionFactory delegate; + + public Jms10ConnectionFactory(ConnectionFactory delegate) { + this.delegate = delegate; + } + + // --- JMS 1.1-only unified ConnectionFactory methods --- + + @Override + public Connection createConnection() throws JMSException { + return delegate.createConnection(); + } + + @Override + public Connection createConnection(String userName, String password) throws JMSException { + return delegate.createConnection(userName, password); + } + + // --- JMS 1.0 QueueConnectionFactory methods --- + @Override + public QueueConnection createQueueConnection() throws JMSException { + return new Jms10Connection(delegate.createConnection()); + } + + @Override + public QueueConnection createQueueConnection(String userName, String password) + throws JMSException { + return new Jms10Connection(delegate.createConnection(userName, password)); + } + + // --- JMS 1.0 TopicConnectionFactory methods --- + + @Override + public TopicConnection createTopicConnection() throws JMSException { + return new Jms10Connection(delegate.createConnection()); + } + + @Override + public TopicConnection createTopicConnection(String userName, String password) + throws JMSException { + return new Jms10Connection(delegate.createConnection(userName, password)); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueReceiver.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueReceiver.java new file mode 100644 index 00000000000..92b8f8ec93b --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueReceiver.java @@ -0,0 +1,59 @@ +package jms10mock; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Queue; +import javax.jms.QueueReceiver; + +/** Wraps a real {@link MessageConsumer} but simulates a JMS 1.0 provider. */ +public class Jms10QueueReceiver implements QueueReceiver { + private final MessageConsumer delegate; + private final Queue queue; + + public Jms10QueueReceiver(MessageConsumer delegate, Queue queue) { + this.delegate = delegate; + this.queue = queue; + } + + @Override + public Queue getQueue() { + return queue; + } + + @Override + public String getMessageSelector() throws JMSException { + return delegate.getMessageSelector(); + } + + @Override + public MessageListener getMessageListener() throws JMSException { + return delegate.getMessageListener(); + } + + @Override + public void setMessageListener(MessageListener listener) throws JMSException { + delegate.setMessageListener(listener); + } + + @Override + public Message receive() throws JMSException { + return delegate.receive(); + } + + @Override + public Message receive(long timeout) throws JMSException { + return delegate.receive(timeout); + } + + @Override + public Message receiveNoWait() throws JMSException { + return delegate.receiveNoWait(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueSender.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueSender.java new file mode 100644 index 00000000000..1f888203483 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueSender.java @@ -0,0 +1,124 @@ +package jms10mock; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueSender; + +/** Wraps a real {@link MessageProducer} but simulates a JMS 1.0 provider. */ +public class Jms10QueueSender implements QueueSender { + private final MessageProducer delegate; + private final Queue queue; + + public Jms10QueueSender(MessageProducer delegate, Queue queue) { + this.delegate = delegate; + this.queue = queue; + } + + // --- JMS 1.1-only methods — not present in JMS 1.0 --- + + @Override + public Destination getDestination() { + throw new AbstractMethodError("JMS 1.0 provider does not implement getDestination()"); + } + + @Override + public void send(Destination destination, Message message) throws JMSException { + delegate.send(destination, message); + } + + @Override + public void send( + Destination destination, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(destination, message, deliveryMode, priority, timeToLive); + } + + // --- JMS 1.0 QueueSender methods --- + + @Override + public Queue getQueue() { + return queue; + } + + @Override + public void send(Message message) throws JMSException { + delegate.send(message); + } + + @Override + public void send(Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(message, deliveryMode, priority, timeToLive); + } + + @Override + public void send(Queue queue, Message message) throws JMSException { + delegate.send(queue, message); + } + + @Override + public void send(Queue queue, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(queue, message, deliveryMode, priority, timeToLive); + } + + // --- MessageProducer config methods --- + + @Override + public void close() throws JMSException { + delegate.close(); + } + + @Override + public void setDisableMessageID(boolean value) throws JMSException { + delegate.setDisableMessageID(value); + } + + @Override + public boolean getDisableMessageID() throws JMSException { + return delegate.getDisableMessageID(); + } + + @Override + public void setDisableMessageTimestamp(boolean value) throws JMSException { + delegate.setDisableMessageTimestamp(value); + } + + @Override + public boolean getDisableMessageTimestamp() throws JMSException { + return delegate.getDisableMessageTimestamp(); + } + + @Override + public void setDeliveryMode(int deliveryMode) throws JMSException { + delegate.setDeliveryMode(deliveryMode); + } + + @Override + public int getDeliveryMode() throws JMSException { + return delegate.getDeliveryMode(); + } + + @Override + public void setPriority(int defaultPriority) throws JMSException { + delegate.setPriority(defaultPriority); + } + + @Override + public int getPriority() throws JMSException { + return delegate.getPriority(); + } + + @Override + public void setTimeToLive(long timeToLive) throws JMSException { + delegate.setTimeToLive(timeToLive); + } + + @Override + public long getTimeToLive() throws JMSException { + return delegate.getTimeToLive(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Session.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Session.java new file mode 100644 index 00000000000..65629319237 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Session.java @@ -0,0 +1,229 @@ +package jms10mock; + +import java.io.Serializable; +import javax.jms.BytesMessage; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.MapMessage; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Queue; +import javax.jms.QueueBrowser; +import javax.jms.QueueReceiver; +import javax.jms.QueueSender; +import javax.jms.QueueSession; +import javax.jms.Session; +import javax.jms.StreamMessage; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; +import javax.jms.TextMessage; +import javax.jms.Topic; +import javax.jms.TopicPublisher; +import javax.jms.TopicSession; +import javax.jms.TopicSubscriber; + +/** Wraps a real {@link Session} but simulates a JMS 1.0 provider. */ +public class Jms10Session implements QueueSession, TopicSession { + private final Session delegate; + + public Jms10Session(Session delegate) { + this.delegate = delegate; + } + + // --- JMS 1.1-only unified Session methods — not present in JMS 1.0 --- + + @Override + public MessageProducer createProducer(Destination destination) throws JMSException { + return delegate.createProducer(destination); + } + + @Override + public MessageConsumer createConsumer(Destination destination) throws JMSException { + return delegate.createConsumer(destination); + } + + @Override + public MessageConsumer createConsumer(Destination destination, String messageSelector) + throws JMSException { + return delegate.createConsumer(destination, messageSelector); + } + + @Override + public MessageConsumer createConsumer( + Destination destination, String messageSelector, boolean noLocal) throws JMSException { + return delegate.createConsumer(destination, messageSelector, noLocal); + } + + // --- JMS 1.0 QueueSession methods --- + + @Override + public Queue createQueue(String queueName) throws JMSException { + return delegate.createQueue(queueName); + } + + @Override + public QueueReceiver createReceiver(Queue queue) throws JMSException { + return new Jms10QueueReceiver(delegate.createConsumer(queue), queue); + } + + @Override + public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException { + return new Jms10QueueReceiver(delegate.createConsumer(queue, messageSelector), queue); + } + + @Override + public QueueSender createSender(Queue queue) throws JMSException { + return new Jms10QueueSender(delegate.createProducer(queue), queue); + } + + @Override + public QueueBrowser createBrowser(Queue queue) throws JMSException { + return delegate.createBrowser(queue); + } + + @Override + public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException { + return delegate.createBrowser(queue, messageSelector); + } + + @Override + public TemporaryQueue createTemporaryQueue() throws JMSException { + return delegate.createTemporaryQueue(); + } + + // --- JMS 1.0 TopicSession methods --- + + @Override + public Topic createTopic(String topicName) throws JMSException { + return delegate.createTopic(topicName); + } + + @Override + public TopicSubscriber createSubscriber(Topic topic) throws JMSException { + return new Jms10TopicSubscriber(delegate.createConsumer(topic), topic, false); + } + + @Override + public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) + throws JMSException { + return new Jms10TopicSubscriber( + delegate.createConsumer(topic, messageSelector, noLocal), topic, noLocal); + } + + @Override + public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException { + return new Jms10TopicSubscriber(delegate.createDurableSubscriber(topic, name), topic, false); + } + + @Override + public TopicSubscriber createDurableSubscriber( + Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException { + return new Jms10TopicSubscriber( + delegate.createDurableSubscriber(topic, name, messageSelector, noLocal), topic, noLocal); + } + + @Override + public TopicPublisher createPublisher(Topic topic) throws JMSException { + return new Jms10TopicPublisher(delegate.createProducer(topic), topic); + } + + @Override + public TemporaryTopic createTemporaryTopic() throws JMSException { + return delegate.createTemporaryTopic(); + } + + @Override + public void unsubscribe(String name) throws JMSException { + delegate.unsubscribe(name); + } + + // --- Common Session methods --- + + @Override + public BytesMessage createBytesMessage() throws JMSException { + return delegate.createBytesMessage(); + } + + @Override + public MapMessage createMapMessage() throws JMSException { + return delegate.createMapMessage(); + } + + @Override + public Message createMessage() throws JMSException { + return delegate.createMessage(); + } + + @Override + public ObjectMessage createObjectMessage() throws JMSException { + return delegate.createObjectMessage(); + } + + @Override + public ObjectMessage createObjectMessage(Serializable object) throws JMSException { + return delegate.createObjectMessage(object); + } + + @Override + public StreamMessage createStreamMessage() throws JMSException { + return delegate.createStreamMessage(); + } + + @Override + public TextMessage createTextMessage() throws JMSException { + return delegate.createTextMessage(); + } + + @Override + public TextMessage createTextMessage(String text) throws JMSException { + return delegate.createTextMessage(text); + } + + @Override + public boolean getTransacted() throws JMSException { + return delegate.getTransacted(); + } + + @Override + public int getAcknowledgeMode() { + throw new AbstractMethodError("JMS 1.0 provider does not implement getAcknowledgeMode()"); + } + + @Override + public void commit() throws JMSException { + delegate.commit(); + } + + @Override + public void rollback() throws JMSException { + delegate.rollback(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } + + @Override + public void recover() throws JMSException { + delegate.recover(); + } + + @Override + public MessageListener getMessageListener() throws JMSException { + return delegate.getMessageListener(); + } + + @Override + public void setMessageListener(MessageListener listener) throws JMSException { + delegate.setMessageListener(listener); + } + + @Override + public void run() { + delegate.run(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicPublisher.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicPublisher.java new file mode 100644 index 00000000000..6f3c1e38663 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicPublisher.java @@ -0,0 +1,137 @@ +package jms10mock; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Topic; +import javax.jms.TopicPublisher; + +/** Wraps a real {@link MessageProducer} but simulates a JMS 1.0 provider. */ +public class Jms10TopicPublisher implements TopicPublisher { + private final MessageProducer delegate; + private final Topic topic; + + public Jms10TopicPublisher(MessageProducer delegate, Topic topic) { + this.delegate = delegate; + this.topic = topic; + } + + // --- JMS 1.1-only methods — not present in JMS 1.0 --- + + @Override + public Destination getDestination() { + throw new AbstractMethodError("JMS 1.0 provider does not implement getDestination()"); + } + + @Override + public void send(Destination destination, Message message) throws JMSException { + delegate.send(destination, message); + } + + @Override + public void send( + Destination destination, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(destination, message, deliveryMode, priority, timeToLive); + } + + // --- JMS 1.0 TopicPublisher methods --- + + @Override + public Topic getTopic() { + return topic; + } + + @Override + public void publish(Message message) throws JMSException { + delegate.send(message); + } + + @Override + public void publish(Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(message, deliveryMode, priority, timeToLive); + } + + @Override + public void publish(Topic topic, Message message) throws JMSException { + delegate.send(topic, message); + } + + @Override + public void publish(Topic topic, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(topic, message, deliveryMode, priority, timeToLive); + } + + // --- MessageProducer send methods (also available via publish in 1.0) --- + + @Override + public void send(Message message) throws JMSException { + delegate.send(message); + } + + @Override + public void send(Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(message, deliveryMode, priority, timeToLive); + } + + // --- MessageProducer config methods --- + + @Override + public void close() throws JMSException { + delegate.close(); + } + + @Override + public void setDisableMessageID(boolean value) throws JMSException { + delegate.setDisableMessageID(value); + } + + @Override + public boolean getDisableMessageID() throws JMSException { + return delegate.getDisableMessageID(); + } + + @Override + public void setDisableMessageTimestamp(boolean value) throws JMSException { + delegate.setDisableMessageTimestamp(value); + } + + @Override + public boolean getDisableMessageTimestamp() throws JMSException { + return delegate.getDisableMessageTimestamp(); + } + + @Override + public void setDeliveryMode(int deliveryMode) throws JMSException { + delegate.setDeliveryMode(deliveryMode); + } + + @Override + public int getDeliveryMode() throws JMSException { + return delegate.getDeliveryMode(); + } + + @Override + public void setPriority(int defaultPriority) throws JMSException { + delegate.setPriority(defaultPriority); + } + + @Override + public int getPriority() throws JMSException { + return delegate.getPriority(); + } + + @Override + public void setTimeToLive(long timeToLive) throws JMSException { + delegate.setTimeToLive(timeToLive); + } + + @Override + public long getTimeToLive() throws JMSException { + return delegate.getTimeToLive(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicSubscriber.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicSubscriber.java new file mode 100644 index 00000000000..97ca5ea2343 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicSubscriber.java @@ -0,0 +1,66 @@ +package jms10mock; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Topic; +import javax.jms.TopicSubscriber; + +/** Wraps a real {@link MessageConsumer} but simulates a JMS 1.0 provider. */ +public class Jms10TopicSubscriber implements TopicSubscriber { + private final MessageConsumer delegate; + private final Topic topic; + private final boolean noLocal; + + public Jms10TopicSubscriber(MessageConsumer delegate, Topic topic, boolean noLocal) { + this.delegate = delegate; + this.topic = topic; + this.noLocal = noLocal; + } + + @Override + public Topic getTopic() { + return topic; + } + + @Override + public boolean getNoLocal() { + return noLocal; + } + + @Override + public String getMessageSelector() throws JMSException { + return delegate.getMessageSelector(); + } + + @Override + public MessageListener getMessageListener() throws JMSException { + return delegate.getMessageListener(); + } + + @Override + public void setMessageListener(MessageListener listener) throws JMSException { + delegate.setMessageListener(listener); + } + + @Override + public Message receive() throws JMSException { + return delegate.receive(); + } + + @Override + public Message receive(long timeout) throws JMSException { + return delegate.receive(timeout); + } + + @Override + public Message receiveNoWait() throws JMSException { + return delegate.receiveNoWait(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } +} From 04a3a803823d378ec6667f33917068a59dac85c6 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 19 May 2026 14:00:55 +0200 Subject: [PATCH 2/4] move stubs to test fixture --- dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle | 3 +++ .../{test => testFixtures}/java/jms10mock/Jms10Connection.java | 0 .../java/jms10mock/Jms10ConnectionFactory.java | 0 .../java/jms10mock/Jms10QueueReceiver.java | 0 .../java/jms10mock/Jms10QueueSender.java | 0 .../{test => testFixtures}/java/jms10mock/Jms10Session.java | 0 .../java/jms10mock/Jms10TopicPublisher.java | 0 .../java/jms10mock/Jms10TopicSubscriber.java | 0 8 files changed, 3 insertions(+) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10Connection.java (100%) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10ConnectionFactory.java (100%) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10QueueReceiver.java (100%) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10QueueSender.java (100%) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10Session.java (100%) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10TopicPublisher.java (100%) rename dd-java-agent/instrumentation/jms/javax-jms-1.1/src/{test => testFixtures}/java/jms10mock/Jms10TopicSubscriber.java (100%) diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle index 739bc235846..74b9d050a9a 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle @@ -14,6 +14,7 @@ muzzle { } apply from: "$rootDir/gradle/java.gradle" +apply plugin: 'java-test-fixtures' repositories { maven { @@ -33,6 +34,8 @@ tasks.named("latestDepTest", Test) { dependencies { compileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + testFixturesCompileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + testImplementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation') testImplementation group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5' testImplementation group: 'org.apache.activemq', name: 'activemq-pool', version: '5.14.5' diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Connection.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Connection.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10ConnectionFactory.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10ConnectionFactory.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueReceiver.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueReceiver.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueSender.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10QueueSender.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Session.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10Session.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicPublisher.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicPublisher.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicSubscriber.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java similarity index 100% rename from dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/jms10mock/Jms10TopicSubscriber.java rename to dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java From 9e4d9fc7c19e5feb46e5149000169ea4057d089a Mon Sep 17 00:00:00 2001 From: Jordan Wong Date: Wed, 27 May 2026 08:54:31 -0400 Subject: [PATCH 3/4] eval: delete jms for blind test (v2, master 04a3a80382) --- .../jms/jakarta-jms-3.0/build.gradle | 40 - .../jms/jakarta-jms-3.0/gradle.lockfile | 154 --- .../instrumentation/jms/JakartaJmsModule.java | 24 - .../src/test/groovy/JMS2Test.groovy | 265 ---- .../src/test/groovy/MDB1.groovy | 16 - .../src/test/groovy/MDB2.groovy | 14 - .../src/test/groovy/MDBBad.groovy | 14 - .../src/test/groovy/MDBJmsMsg.groovy | 230 ---- .../src/test/groovy/MDBTest.groovy | 78 -- .../test/groovy/SpringListenerJMS2Test.groovy | 42 - .../test/groovy/SpringTemplateJMS2Test.groovy | 127 -- .../src/test/groovy/listener/Config.groovy | 91 -- .../test/groovy/listener/TestListener.groovy | 28 - .../jms/javax-jms-1.1/build.gradle | 54 - .../jms/javax-jms-1.1/gradle.lockfile | 163 --- .../src/latestDepTest/groovy/JMS2Test.groovy | 257 ---- .../groovy/SpringListenerJMS2Test.groovy | 44 - .../groovy/SpringTemplateJMS2Test.groovy | 125 -- .../groovy/listener/Config.groovy | 91 -- .../groovy/listener/TestListener.groovy | 28 - .../jms/DatadogMessageListener.java | 86 -- .../instrumentation/jms/JMSDecorator.java | 321 ----- .../trace/instrumentation/jms/JMSLogger.java | 13 - .../JMSMessageConsumerInstrumentation.java | 244 ---- .../JMSMessageProducerInstrumentation.java | 233 ---- .../instrumentation/jms/JavaxJmsModule.java | 63 - .../MDBMessageConsumerInstrumentation.java | 115 -- .../jms/MessageExtractAdapter.java | 81 -- .../jms/MessageInjectAdapter.java | 45 - .../jms/MessageInstrumentation.java | 53 - .../jms/SessionInstrumentation.java | 220 ---- .../src/test/groovy/JMS1Test.groovy | 1110 ----------------- .../src/test/groovy/JMSDecoratorTest.groovy | 99 -- .../javax-jms-1.1/src/test/groovy/MDB1.groovy | 16 - .../javax-jms-1.1/src/test/groovy/MDB2.groovy | 14 - .../src/test/groovy/MDBBad.groovy | 14 - .../src/test/groovy/MDBJmsMsg.groovy | 224 ---- .../src/test/groovy/MDBTest.groovy | 78 -- .../test/groovy/SpringListenerJMS1Test.groovy | 49 - .../test/groovy/SpringTemplateJMS1Test.groovy | 90 -- .../test/groovy/TimeInQueueForkedTest.groovy | 728 ----------- .../src/test/groovy/listener/Config.groovy | 57 - .../test/groovy/listener/TestListener.groovy | 28 - .../java/jms10mock/Jms10Connection.java | 130 -- .../jms10mock/Jms10ConnectionFactory.java | 61 - .../java/jms10mock/Jms10QueueReceiver.java | 59 - .../java/jms10mock/Jms10QueueSender.java | 124 -- .../java/jms10mock/Jms10Session.java | 229 ---- .../java/jms10mock/Jms10TopicPublisher.java | 137 -- .../java/jms10mock/Jms10TopicSubscriber.java | 66 - 50 files changed, 6672 deletions(-) delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy delete mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java delete mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle deleted file mode 100644 index 982b43523a2..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -muzzle { - pass { - name = "jakarta.jms" - group = "jakarta.jms" - module = "jakarta.jms-api" - versions = "[3.0.0,4)" - javaVersion = "17" - assertInverse = true - } -} - -apply from: "$rootDir/gradle/java.gradle" - -testJvmConstraints { - minJavaVersion = JavaVersion.VERSION_17 -} - -repositories { - maven { - // only place that has org.jboss.naming:jnpserver:5.0.3.GA publicly accessible - name = 'jboss-releases' - url = 'https://repository.jboss.org/nexus/content/repositories/releases/' - } -} - -tasks.named("compileTestGroovy", GroovyCompile) { - configureCompiler(it, 17) -} - -dependencies { - implementation project(':dd-java-agent:instrumentation:jms:javax-jms-1.1') - - testImplementation 'jakarta.jms:jakarta.jms-api:3.0.0' - testImplementation 'jakarta.ejb:jakarta.ejb-api:4.0.0' - testImplementation group: 'org.springframework', name: 'spring-jms', version: '6.0.11' - testImplementation group: 'org.springframework', name: 'spring-context', version: '6.0.11' - testImplementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.1.1' - testImplementation group: 'org.hornetq', name: 'hornetq-jakarta-client', version: '2.4.9.Final' - testImplementation group: 'org.hornetq', name: 'hornetq-jms-server', version: '2.4.9.Final' -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile deleted file mode 100644 index 95acd48addf..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile +++ /dev/null @@ -1,154 +0,0 @@ -# This is a Gradle generated file for dependency locking. -# Manual edits can break the build and are not advised. -# This file is expected to be part of source control. -cafe.cryptography:curve25519-elisabeth:0.1.0=testRuntimeClasspath -cafe.cryptography:ed25519-elisabeth:0.1.0=testRuntimeClasspath -ch.qos.logback:logback-classic:1.2.13=testCompileClasspath,testRuntimeClasspath -ch.qos.logback:logback-core:1.2.13=testCompileClasspath,testRuntimeClasspath -com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okhttp3:okhttp:3.12.15=testCompileClasspath,testRuntimeClasspath -com.datadoghq.okio:okio:1.17.6=testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:java-dogstatsd-client:4.4.5=testRuntimeClasspath -com.datadoghq:sketches-java:0.8.3=testRuntimeClasspath -com.github.javaparser:javaparser-core:3.25.6=codenarc -com.github.jnr:jffi:1.3.14=testRuntimeClasspath -com.github.jnr:jnr-a64asm:1.0.0=testRuntimeClasspath -com.github.jnr:jnr-constants:0.10.4=testRuntimeClasspath -com.github.jnr:jnr-enxio:0.32.19=testRuntimeClasspath -com.github.jnr:jnr-ffi:2.2.18=testRuntimeClasspath -com.github.jnr:jnr-posix:3.1.21=testRuntimeClasspath -com.github.jnr:jnr-unixsocket:0.38.24=testRuntimeClasspath -com.github.jnr:jnr-x86asm:1.0.2=testRuntimeClasspath -com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath -com.github.spotbugs:spotbugs:4.9.8=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,testAnnotationProcessor,testCompileClasspath -com.google.auto.service:auto-service:1.1.1=annotationProcessor,testAnnotationProcessor -com.google.auto:auto-common:1.2.1=annotationProcessor,testAnnotationProcessor -com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.13.2=spotbugs -com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.41.0=spotbugs -com.google.guava:failureaccess:1.0.1=annotationProcessor,testAnnotationProcessor -com.google.guava:guava:20.0=testCompileClasspath,testRuntimeClasspath -com.google.guava:guava:32.0.1-jre=annotationProcessor,testAnnotationProcessor -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,testAnnotationProcessor -com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,testAnnotationProcessor -com.google.re2j:re2j:1.7=testRuntimeClasspath -com.squareup.moshi:moshi:1.11.0=testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:logging-interceptor:3.12.12=testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:okhttp:3.12.12=testCompileClasspath,testRuntimeClasspath -com.squareup.okio:okio:1.17.5=testCompileClasspath,testRuntimeClasspath -com.thoughtworks.qdox:qdox:1.12.1=codenarc -commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.11.0=testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.20.0=spotbugs -de.thetaphi:forbiddenapis:3.10=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath -io.netty:netty-all:4.0.30.Final=testCompileClasspath,testRuntimeClasspath -io.sqreen:libsqreen:17.3.0=testRuntimeClasspath -jakarta.annotation:jakarta.annotation-api:2.1.1=testCompileClasspath,testRuntimeClasspath -jakarta.ejb:jakarta.ejb-api:4.0.0=testCompileClasspath,testRuntimeClasspath -jakarta.inject:jakarta.inject-api:2.0.0=testCompileClasspath,testRuntimeClasspath -jakarta.jms:jakarta.jms-api:3.0.0=testCompileClasspath,testRuntimeClasspath -jakarta.transaction:jakarta.transaction-api:2.0.0=testCompileClasspath,testRuntimeClasspath -javax.inject:javax.inject:1=testCompileClasspath,testRuntimeClasspath -javax.servlet:javax.servlet-api:3.1.0=testCompileClasspath,testRuntimeClasspath -jaxen:jaxen:2.0.0=spotbugs -junit:junit:4.13.2=testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna-platform:5.8.0=testRuntimeClasspath -net.java.dev.jna:jna:5.8.0=testRuntimeClasspath -net.sf.saxon:Saxon-HE:12.9=spotbugs -org.apache.ant:ant-antlr:1.10.14=codenarc -org.apache.ant:ant-junit:1.10.14=codenarc -org.apache.bcel:bcel:6.11.0=spotbugs -org.apache.commons:commons-lang3:3.19.0=spotbugs -org.apache.commons:commons-text:1.14.0=spotbugs -org.apache.logging.log4j:log4j-api:2.25.2=spotbugs -org.apache.logging.log4j:log4j-core:2.25.2=spotbugs -org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath -org.checkerframework:checker-qual:3.33.0=annotationProcessor,testAnnotationProcessor -org.codehaus.groovy:groovy-ant:3.0.23=codenarc -org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc -org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath -org.codehaus.groovy:groovy-templates:3.0.23=codenarc -org.codehaus.groovy:groovy-xml:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath -org.codenarc:CodeNarc:3.7.0=codenarc -org.dom4j:dom4j:2.2.0=spotbugs -org.gmetrics:GMetrics:2.1.0=codenarc -org.hamcrest:hamcrest-core:1.3=testRuntimeClasspath -org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-commons:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-core-client:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-jakarta-client:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-jms-client:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-jms-server:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-journal:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-native:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-server:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.jboss.logging:jboss-logging:3.1.0.GA=testCompileClasspath,testRuntimeClasspath -org.jboss.naming:jnpserver:5.0.3.GA=testCompileClasspath,testRuntimeClasspath -org.jboss.spec.javax.jms:jboss-jms-api_2.0_spec:1.0.0.Final=testCompileClasspath,testRuntimeClasspath -org.jboss.spec.javax.resource:jboss-connector-api_1.5_spec:1.0.0.Final=testCompileClasspath,testRuntimeClasspath -org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.1.Beta1=testCompileClasspath,testRuntimeClasspath -org.jboss:jboss-common-core:2.2.10.GA=testCompileClasspath,testRuntimeClasspath -org.jboss:jboss-transaction-spi:7.0.0.Final=testCompileClasspath,testRuntimeClasspath -org.jctools:jctools-core-jdk11:4.0.6=testRuntimeClasspath -org.jctools:jctools-core:4.0.6=testRuntimeClasspath -org.jgroups:jgroups:3.3.4.Final=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath -org.junit.platform:junit-platform-runner:1.14.1=testRuntimeClasspath -org.junit.platform:junit-platform-suite-api:1.14.1=testRuntimeClasspath -org.junit.platform:junit-platform-suite-commons:1.14.1=testRuntimeClasspath -org.junit:junit-bom:5.14.0=spotbugs -org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath -org.mockito:mockito-core:4.4.0=testRuntimeClasspath -org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.7.1=testRuntimeClasspath -org.ow2.asm:asm-analysis:9.9=spotbugs -org.ow2.asm:asm-commons:9.9=spotbugs -org.ow2.asm:asm-commons:9.9.1=testRuntimeClasspath -org.ow2.asm:asm-tree:9.9=spotbugs -org.ow2.asm:asm-tree:9.9.1=testRuntimeClasspath -org.ow2.asm:asm-util:9.7.1=testRuntimeClasspath -org.ow2.asm:asm-util:9.9=spotbugs -org.ow2.asm:asm:9.9=spotbugs -org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath -org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath -org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath -org.slf4j:slf4j-api:1.7.32=testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j -org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath -org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath -org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-aop:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-beans:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-context:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-core:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-expression:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-jcl:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-jms:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-messaging:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-tx:6.0.11=testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-junit:1.2.1=testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-parser:1.2.0=testCompileClasspath,testRuntimeClasspath -org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java deleted file mode 100644 index 7b8ead719b5..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java +++ /dev/null @@ -1,24 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static java.util.Collections.singletonMap; - -import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.InstrumenterModule; -import java.util.Map; - -@AutoService(InstrumenterModule.class) -public class JakartaJmsModule extends JavaxJmsModule { - public JakartaJmsModule() { - super("jakarta", "jakarta-jms", "jms"); - } - - @Override - public String muzzleDirective() { - return "jakarta.jms"; - } - - @Override - public Map adviceShading() { - return singletonMap("javax", "jakarta"); - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy deleted file mode 100644 index fe3229077a9..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy +++ /dev/null @@ -1,265 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.api.config.TraceInstrumentationConfig -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.api.DDSpanTypes -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import spock.lang.Shared - -import jakarta.jms.Message -import jakarta.jms.MessageListener -import jakarta.jms.Session -import jakarta.jms.TextMessage -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference - -class JMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - Session session - - TextMessage message = session.createTextMessage(messageText) - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.someQueue", "jms.queue.someQueue", true) - clientSession.createQueue("jms.topic.someTopic", "jms.topic.someTopic", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - def producer = session.createProducer(null) - def consumer = session.createConsumer(destination) - - producer.send(destination, message) - - Message receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending to a MessageListener on #jmsResourceName generates a span"() { - setup: - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - - producer.send(message) - lock.countDown() - - expect: - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "failing to receive message with receiveNoWait on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - Message receivedMessage = consumer.receiveNoWait() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "failing to receive message with wait(timeout) on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - Message receivedMessage = consumer.receive(1) - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "sending a message with disabled timestamp generates spans without specific tag"() { - setup: - def producer = session.createProducer(session.createQueue("someQueue")) - def consumer = session.createConsumer(session.createQueue("someQueue")) - - producer.setDisableMessageTimestamp(true) - boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() - producer.send(message) - - consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - assertTraces(2) { - producerTrace(it, "Queue someQueue") - consumerTrace(it, "Queue someQueue", trace(0)[0], isTimeStampDisabled) - } - - cleanup: - producer.close() - consumer.close() - } - - static producerTrace(ListWriterAssert writer, String jmsResourceName) { - writer.trace(1) { - span { - parent() - serviceName "jms" - operationName "jms.produce" - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - defaultTagsNoPeerService() - } - } - } - } - - static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - writer.trace(1) { - span { - childOf parentSpan - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - - tags { - "${Tags.COMPONENT}" "jms" - "${Tags.SPAN_KIND}" "consumer" - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" {it >= 0 } - } - defaultTagsNoPeerService(true) - } - } - } - } -} - -class JMS2ContextSwapForkedTest extends JMS2Test { - @Override - protected void configurePreAgent() { - super.configurePreAgent() - injectSysConfig(TraceInstrumentationConfig.LEGACY_CONTEXT_MANAGER_ENABLED, "false") - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy deleted file mode 100644 index 88d08751e41..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy +++ /dev/null @@ -1,16 +0,0 @@ -import jakarta.ejb.MessageDrivenBean -import jakarta.ejb.MessageDrivenContext -import jakarta.jms.Message -import jakarta.jms.MessageListener - -class MDB1 implements MessageDrivenBean, MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } - void ejbRemove() {} - void setMessageDrivenContext(MessageDrivenContext ctx) {} -} - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy deleted file mode 100644 index 80877ea400b..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import jakarta.ejb.MessageDriven -import jakarta.jms.Message -import jakarta.jms.MessageListener - -@MessageDriven(mappedName="unusedTopic") -class MDB2 implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy deleted file mode 100644 index 1dbb6b1c643..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import jakarta.jms.Message -import jakarta.jms.MessageListener - -// Not a valid MDB because it only implements MessageListener. -class MDBBad implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy deleted file mode 100644 index 978a0d9ecc6..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy +++ /dev/null @@ -1,230 +0,0 @@ -import jakarta.jms.Message -import jakarta.jms.Destination -import jakarta.jms.JMSException - -class MDBJmsMsg implements Message { - - @Override - String getJMSMessageID() throws JMSException { - return "ABC" - } - - @Override - void setJMSMessageID(String id) throws JMSException { - } - - @Override - long getJMSTimestamp() throws JMSException { - return 0 - } - - @Override - void setJMSTimestamp(long timestamp) throws JMSException { - } - - @Override - byte[] getJMSCorrelationIDAsBytes() throws JMSException { - return new byte[0] - } - - @Override - void setJMSCorrelationIDAsBytes(byte[] correlationID) throws JMSException { - } - - @Override - void setJMSCorrelationID(String correlationID) throws JMSException { - } - - @Override - String getJMSCorrelationID() throws JMSException { - return null - } - - @Override - Destination getJMSReplyTo() throws JMSException { - return null - } - - @Override - void setJMSReplyTo(Destination replyTo) throws JMSException { - } - - @Override - Destination getJMSDestination() throws JMSException { - return null - } - - @Override - void setJMSDestination(Destination destination) throws JMSException { - } - - @Override - int getJMSDeliveryMode() throws JMSException { - return 0 - } - - @Override - void setJMSDeliveryMode(int deliveryMode) throws JMSException { - } - - @Override - boolean getJMSRedelivered() throws JMSException { - return false - } - - @Override - void setJMSRedelivered(boolean redelivered) throws JMSException { - } - - @Override - String getJMSType() throws JMSException { - return null - } - - @Override - void setJMSType(String type) throws JMSException { - } - - @Override - long getJMSExpiration() throws JMSException { - return 0 - } - - @Override - void setJMSExpiration(long expiration) throws JMSException { - } - - @Override - long getJMSDeliveryTime() throws JMSException { - return 0 - } - - @Override - void setJMSDeliveryTime(long deliveryTime) throws JMSException { - } - - @Override - int getJMSPriority() throws JMSException { - return 0 - } - - @Override - void setJMSPriority(int priority) throws JMSException { - } - - @Override - void clearProperties() throws JMSException { - } - - @Override - boolean propertyExists(String name) throws JMSException { - return false - } - - @Override - boolean getBooleanProperty(String name) throws JMSException { - return false - } - - @Override - byte getByteProperty(String name) throws JMSException { - return 0 - } - - @Override - short getShortProperty(String name) throws JMSException { - return 0 - } - - @Override - int getIntProperty(String name) throws JMSException { - return 0 - } - - @Override - long getLongProperty(String name) throws JMSException { - return 0 - } - - @Override - float getFloatProperty(String name) throws JMSException { - return 0 - } - - @Override - double getDoubleProperty(String name) throws JMSException { - return 0 - } - - @Override - String getStringProperty(String name) throws JMSException { - return null - } - - @Override - Object getObjectProperty(String name) throws JMSException { - return null - } - - @Override - Enumeration getPropertyNames() throws JMSException { - return null - } - - @Override - void setBooleanProperty(String name, boolean value) throws JMSException { - } - - @Override - void setByteProperty(String name, byte value) throws JMSException { - } - - @Override - void setShortProperty(String name, short value) throws JMSException { - } - - @Override - void setIntProperty(String name, int value) throws JMSException { - } - - @Override - void setLongProperty(String name, long value) throws JMSException { - } - - @Override - void setFloatProperty(String name, float value) throws JMSException { - } - - @Override - void setDoubleProperty(String name, double value) throws JMSException { - } - - @Override - void setStringProperty(String name, String value) throws JMSException { - } - - @Override - void setObjectProperty(String name, Object value) throws JMSException { - } - - @Override - void acknowledge() throws JMSException { - } - - @Override - void clearBody() throws JMSException { - } - - @Override - def T getBody(Class c) throws JMSException { - return null - } - - @Override - boolean isBodyAssignableTo(Class c) throws JMSException { - return false - } -} - - - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy deleted file mode 100644 index c4b9c6e61e7..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy +++ /dev/null @@ -1,78 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.agent.test.asserts.ListWriterAssert -import spock.lang.Shared - -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -class MDBTest extends InstrumentationSpecification { - - @Shared - def msg = new MDBJmsMsg() - - def "Test an incomplete MDB that should not get traced here"() { - setup: - def beanBad = new MDBBad() - - when: - runUnderTrace("parent") { - beanBad.onMessage(msg) - } - - then: - assertTraces(1) { - workerTrace(it) - } - } - - def "Test MDB1"() { - setup: - def bean1 = new MDB1() - - when: - runUnderTrace("parent") { - bean1.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def "Test MDB2"() { - setup: - def bean2 = new MDB2() - - when: - runUnderTrace("parent") { - bean2.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def workerTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - serviceName "worker.org.gradle.process.internal.worker.GradleWorkerMain" - } - } - } - - def jmsTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - spanType "queue" - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from Temporary Queue" - measured true - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy deleted file mode 100644 index 5532169b96a..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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. - */ - -import datadog.trace.agent.test.InstrumentationSpecification -import listener.Config -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.jms.core.JmsTemplate - -import jakarta.jms.ConnectionFactory - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringListenerJMS2Test extends InstrumentationSpecification { - - def "receiving message in spring listener generates spans"() { - setup: - def context = new AnnotationConfigApplicationContext(Config) - def factory = context.getBean(ConnectionFactory) - def template = new JmsTemplate(factory) - template.convertAndSend("SpringListenerJMS2", "a message") - - expect: - assertTraces(2) { - producerTrace(it, "Queue SpringListenerJMS2") - consumerTrace(it, "Queue SpringListenerJMS2", trace(0)[0]) - } - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy deleted file mode 100644 index a1000cb5865..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy +++ /dev/null @@ -1,127 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification - -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.jms.core.JmsTemplate -import spock.lang.Shared - -import jakarta.jms.Session -import jakarta.jms.Message - -import java.util.concurrent.TimeUnit - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringTemplateJMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - JmsTemplate template - @Shared - Session session - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringTemplateJMS2", "jms.queue.SpringTemplateJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - - template = new JmsTemplate(connectionFactory) - template.receiveTimeout = TimeUnit.SECONDS.toMillis(10) - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - template.convertAndSend(destination, messageText) - Message receivedMessage = template.receive(destination) - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } - - def "send and receive message generates spans"() { - setup: - Thread.start { - TEST_WRITER.waitForTraces(1) - Message msg = template.receive(destination) - assert msg.text == messageText - - // There's a chance this might be reported last, messing up the assertion. - template.send(msg.getJMSReplyTo()) { session -> - template.getMessageConverter().toMessage("responded!", session) - } - } - Message receivedMessage = template.sendAndReceive(destination) { session -> - template.getMessageConverter().toMessage(messageText, session) - } - - expect: - receivedMessage.text == "responded!" - assertTraces(4) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root - consumerTrace(it, "Temporary Queue", trace(2)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy deleted file mode 100644 index 16de4f012e0..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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 listener - -import com.google.common.io.Files -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.jms.annotation.EnableJms -import org.springframework.jms.config.DefaultJmsListenerContainerFactory -import org.springframework.jms.config.JmsListenerContainerFactory - -import jakarta.annotation.PreDestroy -import jakarta.jms.ConnectionFactory - -@Configuration -@ComponentScan -@EnableJms -class Config { - - private HornetQServer server - - @Bean - ConnectionFactory connectionFactory() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - org.hornetq.core.config.Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringListenerJMS2", "jms.queue.SpringListenerJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - } - - @Bean - JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() - factory.setConnectionFactory(connectionFactory) - return factory - } - - @PreDestroy - void destroy() { - server.stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy deleted file mode 100644 index e2eccd25449..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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 listener - -import org.springframework.jms.annotation.JmsListener -import org.springframework.stereotype.Component - -@Component -class TestListener { - - @JmsListener(destination = "SpringListenerJMS2", containerFactory = "containerFactory") - void receiveMessage(String message) { - println "received: " + message - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle deleted file mode 100644 index 74b9d050a9a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle +++ /dev/null @@ -1,54 +0,0 @@ -muzzle { - pass { - name = "javax.jms" - group = "javax.jms" - module = "jms-api" - versions = "[,]" - } - pass { - name = "javax.jms" - group = "javax.jms" - module = "javax.jms-api" - versions = "[,]" - } -} - -apply from: "$rootDir/gradle/java.gradle" -apply plugin: 'java-test-fixtures' - -repositories { - maven { - // only place that has org.jboss.naming:jnpserver:5.0.3.GA publicly accessible - name = 'jboss-releases' - url = 'https://repository.jboss.org/nexus/content/repositories/releases/' - } -} - -addTestSuite('latestDepTest') -addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') - -tasks.named("latestDepTest", Test) { - finalizedBy 'latestDepForkedTest' -} - -dependencies { - compileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' - - testFixturesCompileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' - - testImplementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation') - testImplementation group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5' - testImplementation group: 'org.apache.activemq', name: 'activemq-pool', version: '5.14.5' - testImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.14.5' - - // required for Java 11+ . Latest version that runs on Java 7 - testImplementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.2' - testImplementation group: 'org.springframework', name: 'spring-jms', version: '4.3.21.RELEASE' // 4.x required for Java 7 - testImplementation group: 'javax.ejb', name: 'javax.ejb-api', version: '3.2' - - latestDepTestImplementation group: 'org.hornetq', name: 'hornetq-jms-client', version: '2.4.7.Final' - latestDepTestImplementation group: 'org.hornetq', name: 'hornetq-jms-server', version: '2.4.7.Final' - - // For this module Groovy 3 needs `org.junit.rules.ExternalResource` from JUnit4. - testImplementation(libs.junit4) -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile b/dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile deleted file mode 100644 index cde2ee01a01..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile +++ /dev/null @@ -1,163 +0,0 @@ -# This is a Gradle generated file for dependency locking. -# Manual edits can break the build and are not advised. -# This file is expected to be part of source control. -cafe.cryptography:curve25519-elisabeth:0.1.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -cafe.cryptography:ed25519-elisabeth:0.1.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -ch.qos.logback:logback-classic:1.2.13=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -ch.qos.logback:logback-core:1.2.13=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okhttp3:okhttp:3.12.15=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okio:okio:1.17.6=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:java-dogstatsd-client:4.4.5=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.datadoghq:sketches-java:0.8.3=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.javaparser:javaparser-core:3.25.6=codenarc -com.github.jnr:jffi:1.3.14=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-a64asm:1.0.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-constants:0.10.4=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-enxio:0.32.19=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-ffi:2.2.18=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-posix:3.1.21=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-unixsocket:0.38.24=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-x86asm:1.0.2=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath -com.github.spotbugs:spotbugs:4.9.8=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,latestDepForkedTestAnnotationProcessor,latestDepForkedTestCompileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath -com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,latestDepForkedTestAnnotationProcessor,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.13.2=spotbugs -com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.41.0=spotbugs -com.google.guava:failureaccess:1.0.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.guava:guava:20.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.re2j:re2j:1.7=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.squareup.moshi:moshi:1.11.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:okhttp:3.12.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okio:okio:1.17.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.thoughtworks.qdox:qdox:1.12.1=codenarc -commons-fileupload:commons-fileupload:1.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.11.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.20.0=spotbugs -commons-logging:commons-logging:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -de.thetaphi:forbiddenapis:3.10=compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -io.leangen.geantyref:geantyref:1.3.16=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-all:4.0.13.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.sqreen:libsqreen:17.3.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -javax.annotation:javax.annotation-api:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.ejb:javax.ejb-api:3.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.inject:javax.inject:1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -javax.jms:jms-api:1.1-rev-1=compileClasspath -javax.servlet:javax.servlet-api:3.1.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.transaction:javax.transaction-api:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -jaxen:jaxen:2.0.0=spotbugs -junit:junit:4.13.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna-platform:5.8.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -net.java.dev.jna:jna:5.8.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -net.sf.saxon:Saxon-HE:12.9=spotbugs -org.apache.activemq.tooling:activemq-junit:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-broker:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-client:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-jms-pool:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-openwire-legacy:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-pool:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.ant:ant-antlr:1.10.14=codenarc -org.apache.ant:ant-junit:1.10.14=codenarc -org.apache.bcel:bcel:6.11.0=spotbugs -org.apache.commons:commons-lang3:3.19.0=spotbugs -org.apache.commons:commons-pool2:2.4.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.commons:commons-text:1.14.0=spotbugs -org.apache.geronimo.specs:geronimo-j2ee-management_1.1_spec:1.0.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.geronimo.specs:geronimo-jta_1.0.1B_spec:1.0.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.logging.log4j:log4j-api:2.25.2=spotbugs -org.apache.logging.log4j:log4j-core:2.25.2=spotbugs -org.apiguardian:apiguardian-api:1.1.2=latestDepForkedTestCompileClasspath,latestDepTestCompileClasspath,testCompileClasspath -org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -org.codehaus.groovy:groovy-ant:3.0.23=codenarc -org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc -org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.25=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.codehaus.groovy:groovy-templates:3.0.23=codenarc -org.codehaus.groovy:groovy-xml:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.25=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.codenarc:CodeNarc:3.7.0=codenarc -org.dom4j:dom4j:2.2.0=spotbugs -org.fusesource.hawtbuf:hawtbuf:1.11=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.gmetrics:GMetrics:2.1.0=codenarc -org.hamcrest:hamcrest-core:1.3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hamcrest:hamcrest:3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-commons:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-core-client:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-jms-client:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-jms-server:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-journal:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-native:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-server:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.logging:jboss-logging:3.1.0.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.naming:jnpserver:5.0.3.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.spec.javax.jms:jboss-jms-api_2.0_spec:1.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.spec.javax.resource:jboss-connector-api_1.5_spec:1.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.1.Beta1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss:jboss-common-core:2.2.10.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss:jboss-transaction-spi:7.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jctools:jctools-core-jdk11:4.0.6=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.jctools:jctools-core:4.0.6=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.jgroups:jgroups:3.3.4.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-launcher:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-runner:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-suite-api:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit:junit-bom:5.14.0=spotbugs -org.junit:junit-bom:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.mockito:mockito-core:4.4.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.objenesis:objenesis:3.3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.7.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.9=spotbugs -org.ow2.asm:asm-commons:9.9=spotbugs -org.ow2.asm:asm-commons:9.9.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-tree:9.9=spotbugs -org.ow2.asm:asm-tree:9.9.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-util:9.7.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-util:9.9=spotbugs -org.ow2.asm:asm:9.9=spotbugs -org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jcl-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jul-to-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:log4j-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath -org.slf4j:slf4j-api:1.7.32=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j -org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath -org.spockframework:spock-bom:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.spockframework:spock-core:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-aop:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-beans:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-context:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-core:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-expression:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-jms:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-messaging:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-tx:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-junit:1.2.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-parser:1.2.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy deleted file mode 100644 index 196ebccae37..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy +++ /dev/null @@ -1,257 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.api.DDSpanTypes -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.hornetq.jms.client.HornetQTextMessage -import spock.lang.Shared - -import javax.jms.Message -import javax.jms.MessageListener -import javax.jms.Session -import javax.jms.TextMessage -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference - -class JMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - Session session - - HornetQTextMessage message = session.createTextMessage(messageText) - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.someQueue", "jms.queue.someQueue", true) - clientSession.createQueue("jms.topic.someTopic", "jms.topic.someTopic", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - producer.send(message) - - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending to a MessageListener on #jmsResourceName generates a span"() { - setup: - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - - producer.send(message) - lock.countDown() - - expect: - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "failing to receive message with receiveNoWait on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receiveNoWait() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "failing to receive message with wait(timeout) on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receive(1) - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "sending a message with disabled timestamp generates spans without specific tag"() { - setup: - def producer = session.createProducer(session.createQueue("someQueue")) - def consumer = session.createConsumer(session.createQueue("someQueue")) - - producer.setDisableMessageTimestamp(true) - boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() - producer.send(message) - - consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - assertTraces(2) { - producerTrace(it, "Queue someQueue") - consumerTrace(it, "Queue someQueue", trace(0)[0], isTimeStampDisabled) - } - - cleanup: - producer.close() - consumer.close() - } - - static producerTrace(ListWriterAssert writer, String jmsResourceName) { - writer.trace(1) { - span { - parent() - serviceName "jms" - operationName "jms.produce" - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - defaultTagsNoPeerService() - } - } - } - } - - static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - writer.trace(1) { - span { - childOf parentSpan - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - - tags { - "${Tags.COMPONENT}" "jms" - "${Tags.SPAN_KIND}" "consumer" - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" {it >= 0 } - } - defaultTagsNoPeerService(true) - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy deleted file mode 100644 index 0856cdab048..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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. - */ - - - -import datadog.trace.agent.test.InstrumentationSpecification -import listener.Config -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.jms.core.JmsTemplate - -import javax.jms.ConnectionFactory - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringListenerJMS2Test extends InstrumentationSpecification { - - def "receiving message in spring listener generates spans"() { - setup: - def context = new AnnotationConfigApplicationContext(Config) - def factory = context.getBean(ConnectionFactory) - def template = new JmsTemplate(factory) - template.convertAndSend("SpringListenerJMS2", "a message") - - expect: - assertTraces(2) { - producerTrace(it, "Queue SpringListenerJMS2") - consumerTrace(it, "Queue SpringListenerJMS2", trace(0)[0]) - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy deleted file mode 100644 index 25163435b0c..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy +++ /dev/null @@ -1,125 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.jms.core.JmsTemplate -import spock.lang.Shared - -import javax.jms.Session -import javax.jms.TextMessage -import java.util.concurrent.TimeUnit - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringTemplateJMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - JmsTemplate template - @Shared - Session session - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringTemplateJMS2", "jms.queue.SpringTemplateJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - - template = new JmsTemplate(connectionFactory) - template.receiveTimeout = TimeUnit.SECONDS.toMillis(10) - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - template.convertAndSend(destination, messageText) - TextMessage receivedMessage = template.receive(destination) - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } - - def "send and receive message generates spans"() { - setup: - Thread.start { - TEST_WRITER.waitForTraces(1) - TextMessage msg = template.receive(destination) - assert msg.text == messageText - - // There's a chance this might be reported last, messing up the assertion. - template.send(msg.getJMSReplyTo()) { session -> - template.getMessageConverter().toMessage("responded!", session) - } - } - TextMessage receivedMessage = template.sendAndReceive(destination) { session -> - template.getMessageConverter().toMessage(messageText, session) - } - - expect: - receivedMessage.text == "responded!" - assertTraces(4) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root - consumerTrace(it, "Temporary Queue", trace(2)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy deleted file mode 100644 index 10dfaeee79a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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 listener - -import com.google.common.io.Files -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.jms.annotation.EnableJms -import org.springframework.jms.config.DefaultJmsListenerContainerFactory -import org.springframework.jms.config.JmsListenerContainerFactory - -import javax.annotation.PreDestroy -import javax.jms.ConnectionFactory - -@Configuration -@ComponentScan -@EnableJms -class Config { - - private HornetQServer server - - @Bean - ConnectionFactory connectionFactory() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - org.hornetq.core.config.Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringListenerJMS2", "jms.queue.SpringListenerJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - } - - @Bean - JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() - factory.setConnectionFactory(connectionFactory) - return factory - } - - @PreDestroy - void destroy() { - server.stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy deleted file mode 100644 index e2eccd25449..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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 listener - -import org.springframework.jms.annotation.JmsListener -import org.springframework.stereotype.Component - -@Component -class TestListener { - - @JmsListener(destination = "SpringListenerJMS2", containerFactory = "containerFactory") - void receiveMessage(String message) { - println "received: " + message - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java deleted file mode 100644 index 954dec71ad0..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java +++ /dev/null @@ -1,86 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; -import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; -import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import datadog.trace.bootstrap.ContextStore; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; -import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; -import datadog.trace.bootstrap.instrumentation.jms.SessionState; -import javax.jms.Message; -import javax.jms.MessageListener; - -public class DatadogMessageListener implements MessageListener { - - private final ContextStore messageAckStore; - private final MessageConsumerState consumerState; - private final MessageListener messageListener; - - public DatadogMessageListener( - ContextStore messageAckStore, - MessageConsumerState consumerState, - MessageListener messageListener) { - this.messageAckStore = messageAckStore; - this.consumerState = consumerState; - this.messageListener = messageListener; - } - - @Override - public void onMessage(Message message) { - AgentSpan span; - AgentSpanContext propagatedContext = null; - if (!consumerState.isPropagationDisabled()) { - propagatedContext = extractContextAndGetSpanContext(message, GETTER); - } - long startMillis = GETTER.extractTimeInQueueStart(message); - if (startMillis == 0 || !TIME_IN_QUEUE_ENABLED) { - span = startSpan("jms", JMS_CONSUME, propagatedContext); - } else { - long batchId = GETTER.extractMessageBatchId(message); - AgentSpan timeInQueue = consumerState.getTimeInQueueSpan(batchId); - if (null == timeInQueue) { - timeInQueue = - startSpan("jms", JMS_DELIVER, propagatedContext, MILLISECONDS.toMicros(startMillis)); - BROKER_DECORATE.afterStart(timeInQueue); - BROKER_DECORATE.onTimeInQueue( - timeInQueue, - consumerState.getBrokerResourceName(), - consumerState.getBrokerServiceName()); - consumerState.setTimeInQueueSpan(batchId, timeInQueue); - } - span = startSpan("jms", JMS_CONSUME, timeInQueue.context()); - } - CONSUMER_DECORATE.afterStart(span); - CONSUMER_DECORATE.onConsume(span, message, consumerState.getConsumerResourceName()); - SessionState sessionState = consumerState.getSessionState(); - if (sessionState.isClientAcknowledge()) { - // consumed spans will be finished by a call to Message.acknowledge - sessionState.finishOnAcknowledge(span); - messageAckStore.put(message, sessionState); - } else if (sessionState.isTransactedSession()) { - // span will be finished by Session.commit/rollback/close - sessionState.finishOnCommit(span); - } - try (AgentScope scope = activateSpan(span)) { - messageListener.onMessage(message); - } catch (RuntimeException | Error thrown) { - CONSUMER_DECORATE.onError(span, thrown); - throw thrown; - } finally { - if (sessionState.isAutoAcknowledge()) { - span.finish(); - consumerState.finishTimeInQueueSpan(false); - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java deleted file mode 100644 index c9dd753b3da..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java +++ /dev/null @@ -1,321 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.RECORD_QUEUE_TIME_MS; - -import datadog.trace.api.Config; -import datadog.trace.api.Functions.Join; -import datadog.trace.api.Functions.PrefixJoin; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.api.naming.SpanNaming; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; -import datadog.trace.bootstrap.instrumentation.api.Tags; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; -import datadog.trace.bootstrap.instrumentation.decorator.MessagingClientDecorator; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Supplier; -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageProducer; -import javax.jms.Queue; -import javax.jms.QueueSender; -import javax.jms.TemporaryQueue; -import javax.jms.TemporaryTopic; -import javax.jms.Topic; -import javax.jms.TopicPublisher; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class JMSDecorator extends MessagingClientDecorator { - private static final Logger log = LoggerFactory.getLogger(JMSDecorator.class); - - public static final CharSequence JMS = UTF8BytesString.create("jms"); - public static final CharSequence JMS_CONSUME = - UTF8BytesString.create( - SpanNaming.instance().namingSchema().messaging().inboundOperation(JMS.toString())); - public static final CharSequence JMS_PRODUCE = - UTF8BytesString.create( - SpanNaming.instance().namingSchema().messaging().outboundOperation(JMS.toString())); - public static final CharSequence JMS_DELIVER = UTF8BytesString.create("jms.deliver"); - - public static final boolean JMS_LEGACY_TRACING = Config.get().isJmsLegacyTracingEnabled(); - - public static final boolean TIME_IN_QUEUE_ENABLED = - Config.get().isTimeInQueueEnabled(!JMS_LEGACY_TRACING, "jms"); - public static final String JMS_PRODUCED_KEY = "x_datadog_jms_produced"; - public static final String JMS_BATCH_ID_KEY = "x_datadog_jms_batch_id"; - - private static final Join QUEUE_JOINER = PrefixJoin.of("Queue "); - private static final Join TOPIC_JOINER = PrefixJoin.of("Topic "); - - private final DDCache resourceNameCache = - DDCaches.newFixedSizeCache(32); - - private final String resourcePrefix; - - private final UTF8BytesString queueTempResourceName; - private final UTF8BytesString topicTempResourceName; - - private final Function queueResourceJoiner; - private final Function topicResourceJoiner; - - private final String spanKind; - private final CharSequence spanType; - private final Supplier serviceNameSupplier; - - public static final JMSDecorator PRODUCER_DECORATE = - new JMSDecorator( - "Produced for ", - Tags.SPAN_KIND_PRODUCER, - InternalSpanTypes.MESSAGE_PRODUCER, - SpanNaming.instance() - .namingSchema() - .messaging() - .outboundService("jms", JMS_LEGACY_TRACING)); - - public static final JMSDecorator CONSUMER_DECORATE = - new JMSDecorator( - "Consumed from ", - Tags.SPAN_KIND_CONSUMER, - InternalSpanTypes.MESSAGE_CONSUMER, - SpanNaming.instance() - .namingSchema() - .messaging() - .inboundService("jms", JMS_LEGACY_TRACING)); - - public static final JMSDecorator BROKER_DECORATE = - new JMSDecorator( - "", - Tags.SPAN_KIND_BROKER, - InternalSpanTypes.MESSAGE_BROKER, - SpanNaming.instance().namingSchema().messaging().timeInQueueService(JMS.toString())); - - public JMSDecorator( - String resourcePrefix, - String spanKind, - CharSequence spanType, - Supplier serviceNameSupplier) { - this.resourcePrefix = resourcePrefix; - - this.queueTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Queue"); - this.topicTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Topic"); - - this.queueResourceJoiner = QUEUE_JOINER.curry(resourcePrefix); - this.topicResourceJoiner = TOPIC_JOINER.curry(resourcePrefix); - - this.spanKind = spanKind; - this.spanType = spanType; - this.serviceNameSupplier = serviceNameSupplier; - } - - public static void logJMSException(JMSException ex) { - if (log.isDebugEnabled()) { - log.debug("JMS exception during instrumentation", ex); - } - } - - public static String messageTechnology(Message m) { - if (null == m) { - return "null"; - } - - String messageClass = m.getClass().getName(); - - if (messageClass.startsWith("com.amazon.sqs")) { - return "sqs"; - } else if (messageClass.startsWith("com.ibm")) { - return "ibmmq"; - } else { - return "unknown"; - } - } - - @Override - protected String[] instrumentationNames() { - return new String[] {"jms"}; - } - - @Override - protected CharSequence spanType() { - return spanType; - } - - @Override - protected String service() { - return serviceNameSupplier.get(); - } - - @Override - protected CharSequence component() { - return JMS; - } - - @Override - protected String spanKind() { - return spanKind; - } - - public void onConsume(AgentSpan span, Message message, CharSequence resourceName) { - if (null != resourceName) { - span.setResourceName(resourceName); - } - - try { - final long produceTime = message.getJMSTimestamp(); - if (produceTime > 0) { - final long consumeTime = TimeUnit.NANOSECONDS.toMillis(span.getStartTime()); - span.setTag(RECORD_QUEUE_TIME_MS, Math.max(0L, consumeTime - produceTime)); - } - } catch (Exception e) { - log.debug("Unable to get jms timestamp", e); - } - } - - public void onProduce(AgentSpan span, CharSequence resourceName) { - if (null != resourceName) { - span.setResourceName(resourceName); - } - } - - public static boolean canInject(Message message) { - // JMS->SQS already stores the trace context in 'X-Amzn-Trace-Id' / 'AWSTraceHeader', - // so skip storing same context again to avoid SQS limit of 10 attributes per message. - return !message.getClass().getName().startsWith("com.amazon.sqs.javamessaging"); - } - - public void onTimeInQueue(AgentSpan span, CharSequence resourceName, String serviceName) { - if (null != resourceName) { - span.setResourceName(resourceName); - } - if (null != serviceName) { - span.setServiceName(serviceName, component()); - } - } - - private static final String TIBCO_TMP_PREFIX = "$TMP$"; - - /** - * Sanitizes destination names to remove Kafka Connect schema-derived suffixes. When Kafka - * Connect's IBM MQ connectors are used with schema converters (Protobuf/JSON Schema), union or - * optional fields may get index suffixes like _messagebody_0 appended to the queue name. - */ - private static String sanitizeDestinationName(String name) { - if (name == null || name.isEmpty()) { - return name; - } - - int len = name.length(); - - // Check if name ends with digits (the schema index suffix) - if (!Character.isDigit(name.charAt(len - 1))) { - return name; - } - - // Find the underscore before the trailing digits - int underscoreBeforeDigits = name.lastIndexOf('_'); - if (underscoreBeforeDigits <= 0) { - return name; - } - - // Verify all characters after the underscore are digits - for (int i = underscoreBeforeDigits + 1; i < len; i++) { - if (!Character.isDigit(name.charAt(i))) { - return name; - } - } - - // Find the underscore before the suffix word - int underscoreBeforeSuffix = name.lastIndexOf('_', underscoreBeforeDigits - 1); - if (underscoreBeforeSuffix < 0) { - return name; - } - - // Check if the suffix word is one of our known Kafka Connect schema suffixes (case insensitive) - int suffixStart = underscoreBeforeSuffix + 1; - int suffixLen = underscoreBeforeDigits - suffixStart; - - if (isKnownKafkaConnectSuffix(name, suffixStart, suffixLen)) { - return name.substring(0, underscoreBeforeSuffix); - } - - return name; - } - - private static boolean isKnownKafkaConnectSuffix(String name, int start, int len) { - return (len == 11 && name.regionMatches(true, start, "messagebody", 0, 11)) - || (len == 4 && name.regionMatches(true, start, "text", 0, 4)) - || (len == 5 && name.regionMatches(true, start, "bytes", 0, 5)) - || (len == 5 && name.regionMatches(true, start, "value", 0, 5)) - || (len == 3 && name.regionMatches(true, start, "map", 0, 3)); - } - - public CharSequence toResourceName(String destinationName, boolean isQueue) { - if (null == destinationName) { - return isQueue ? queueTempResourceName : topicTempResourceName; - } - Function joiner = - isQueue ? queueResourceJoiner : topicResourceJoiner; - // some systems may have queues and topics with the same name - since we won't know which was - // cached first we check the character after the initial prefix to see if it's Q (for Queue) - - // if that's what we expect we can use the cached value, otherwise generate the correct name - CharSequence resourceName = resourceNameCache.computeIfAbsent(destinationName, joiner); - if ((resourceName.charAt(resourcePrefix.length()) == 'Q') == isQueue) { - return resourceName; - } - return joiner.apply(destinationName); - } - - public Destination getDestination(final MessageProducer messageProducer) throws JMSException { - try { - return messageProducer.getDestination(); // >= 1.1 - } catch (AbstractMethodError ignored) { - // <=1.1 getDestination is not available so we need to pay an additional instanceOf - if (messageProducer instanceof QueueSender) { - return ((QueueSender) messageProducer).getQueue(); - } - return ((TopicPublisher) messageProducer).getTopic(); - } - } - - public String getDestinationName(Destination destination) { - String name = null; - try { - if (destination instanceof Queue) { - // WebLogic mixes all JMS Destination interfaces in a single base type which means we can't - // rely on instanceof and have to instead check the result of getQueueName vs getTopicName - if (!(destination instanceof TemporaryQueue) || isWebLogicDestination(destination)) { - name = ((Queue) destination).getQueueName(); - } - } - // check Topic name if Queue name is null because this might be a WebLogic destination - if (null == name && destination instanceof Topic) { - if (!(destination instanceof TemporaryTopic) || isWebLogicDestination(destination)) { - name = ((Topic) destination).getTopicName(); - } - } - } catch (Exception e) { - log.debug("Unable to get jms destination name", e); - } - if (null != name && !name.startsWith(TIBCO_TMP_PREFIX)) { - // Sanitize Kafka Connect schema-derived suffixes from queue/topic names - return sanitizeDestinationName(name); - } - return null; - } - - public boolean isQueue(Destination destination) { - try { - // handle WebLogic by treating everything as a Queue unless it's a Topic with a name - return !(destination instanceof Topic) || null == ((Topic) destination).getTopicName(); - } catch (Exception e) { - return true; // assume it's a Queue if we can't check the details - } - } - - private static boolean isWebLogicDestination(Destination destination) { - return destination.getClass().getName().startsWith("weblogic.jms.common."); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java deleted file mode 100644 index 6618045254d..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java +++ /dev/null @@ -1,13 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class JMSLogger { - private static final Logger log = LoggerFactory.getLogger(JMSLogger.class); - - public static void logIterationSpan(AgentSpan span) { - log.debug("Expecting the following `ITERATION` span to be finished {}", span); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java deleted file mode 100644 index 75a59e7406d..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java +++ /dev/null @@ -1,244 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; -import static datadog.trace.api.datastreams.DataStreamsTags.create; -import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateNext; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.closePrevious; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; -import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.spanFromContext; -import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; -import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; -import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; -import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.api.Config; -import datadog.trace.api.InstrumenterConfig; -import datadog.trace.api.datastreams.DataStreamsContext; -import datadog.trace.api.datastreams.DataStreamsTags; -import datadog.trace.bootstrap.CallDepthThreadLocalMap; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; -import datadog.trace.bootstrap.instrumentation.jms.SessionState; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageListener; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public final class JMSMessageConsumerInstrumentation - implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - private final String namespace; - - public JMSMessageConsumerInstrumentation(String namespace) { - this.namespace = namespace; - } - - @Override - public String hierarchyMarkerType() { - return namespace + ".jms.MessageConsumer"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return implementsInterface(named(hierarchyMarkerType())); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - named("receive").and(takesArguments(0).or(takesArguments(1))).and(isPublic()), - JMSMessageConsumerInstrumentation.class.getName() + "$ConsumerAdvice"); - transformer.applyAdvice( - named("receiveNoWait").and(takesArguments(0)).and(isPublic()), - JMSMessageConsumerInstrumentation.class.getName() + "$ConsumerAdvice"); - transformer.applyAdvice( - named("close").and(takesArguments(0)).and(isPublic()), - JMSMessageConsumerInstrumentation.class.getName() + "$Close"); - transformer.applyAdvice( - isMethod() - .and(named("setMessageListener")) - .and(takesArgument(0, hasInterface(named(namespace + ".jms.MessageListener")))), - getClass().getName() + "$DecorateMessageListener"); - } - - public static class ConsumerAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static MessageConsumerState beforeReceive(@Advice.This final MessageConsumer consumer) { - MessageConsumerState consumerState = - InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) - .get(consumer); - - // ignore consumers who aren't bound to a tracked session via consumerState - if (null == consumerState) { - return null; - } - - boolean finishSpan = consumerState.getSessionState().isAutoAcknowledge(); - if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { - closePrevious(finishSpan); - } else { - final AgentSpan previousSpan = spanFromContext(getRootContext().swap()); - if (previousSpan != null) { - CONSUMER_DECORATE.beforeFinish(previousSpan); - previousSpan.finishWithEndToEnd(); - } - } - if (finishSpan) { - consumerState.finishTimeInQueueSpan(false); - } - - // don't create spans for nested receive calls, even if different consumers are involved - final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageConsumer.class); - if (callDepth > 0) { - return null; - } - - return consumerState; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void afterReceive( - @Advice.Enter final MessageConsumerState consumerState, - @Advice.This final MessageConsumer consumer, - @Advice.Return final Message message, - @Advice.Thrown final Throwable throwable) { - - if (consumerState == null) { - // either we're not tracking the consumer or this is a nested receive - return; - } - - // outermost receive call - make sure we reset call-depth before returning - CallDepthThreadLocalMap.reset(MessageConsumer.class); - - if (message == null) { - // don't create spans (traces) for each poll if the queue is empty - return; - } - - AgentSpan span; - AgentSpanContext propagatedContext = null; - if (!consumerState.isPropagationDisabled()) { - propagatedContext = extractContextAndGetSpanContext(message, GETTER); - } - long startMillis = GETTER.extractTimeInQueueStart(message); - if (startMillis == 0 || !TIME_IN_QUEUE_ENABLED) { - span = startSpan("jms", JMS_CONSUME, propagatedContext); - } else { - long batchId = GETTER.extractMessageBatchId(message); - AgentSpan timeInQueue = consumerState.getTimeInQueueSpan(batchId); - if (null == timeInQueue) { - timeInQueue = - startSpan("jms", JMS_DELIVER, propagatedContext, MILLISECONDS.toMicros(startMillis)); - BROKER_DECORATE.afterStart(timeInQueue); - BROKER_DECORATE.onTimeInQueue( - timeInQueue, - consumerState.getBrokerResourceName(), - consumerState.getBrokerServiceName()); - consumerState.setTimeInQueueSpan(batchId, timeInQueue); - } - span = startSpan("jms", JMS_CONSUME, timeInQueue.context()); - } - - CONSUMER_DECORATE.afterStart(span); - CONSUMER_DECORATE.onConsume(span, message, consumerState.getConsumerResourceName()); - - if (Config.get().isDataStreamsEnabled()) { - final String tech = messageTechnology(message); - if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ - DataStreamsTags tags = - create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); - DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); - } - } - - CONSUMER_DECORATE.onError(span, throwable); - - if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { - activateNext(span); // scope is left open until next message or it times out - } else { - final AgentSpan previousSpan = spanFromContext(span.swap()); - if (previousSpan != null) { - CONSUMER_DECORATE.beforeFinish(previousSpan); - previousSpan.finishWithEndToEnd(); - } - } - JMSLogger.logIterationSpan(span); - - SessionState sessionState = consumerState.getSessionState(); - if (sessionState.isClientAcknowledge()) { - // consumed spans will be finished by a call to Message.acknowledge - sessionState.finishOnAcknowledge(span); - InstrumentationContext.get(Message.class, SessionState.class).put(message, sessionState); - } else if (sessionState.isTransactedSession()) { - // span will be finished by Session.commit/rollback/close - sessionState.finishOnCommit(span); - } - // for AUTO_ACKNOWLEDGE, span is not finished until next call to receive, or close - } - } - - public static class Close { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void beforeClose(@Advice.This final MessageConsumer consumer) { - MessageConsumerState consumerState = - InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) - .get(consumer); - if (null != consumerState) { - boolean finishSpan = consumerState.getSessionState().isAutoAcknowledge(); - if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { - closePrevious(finishSpan); - } else { - final AgentSpan previousSpan = spanFromContext(getRootContext().swap()); - if (previousSpan != null) { - CONSUMER_DECORATE.beforeFinish(previousSpan); - previousSpan.finishWithEndToEnd(); - } - } - if (finishSpan) { - consumerState.finishTimeInQueueSpan(true); - } - } - } - } - - public static class DecorateMessageListener { - @Advice.OnMethodEnter - public static void setMessageListener( - @Advice.This MessageConsumer messageConsumer, - @Advice.Argument(value = 0, readOnly = false) MessageListener listener) { - if (null != listener && !(listener instanceof DatadogMessageListener)) { - MessageConsumerState consumerState = - InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) - .get(messageConsumer); - if (null != consumerState) { - listener = - new DatadogMessageListener( - InstrumentationContext.get(Message.class, SessionState.class), - consumerState, - listener); - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java deleted file mode 100644 index 3dbfa0579f5..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java +++ /dev/null @@ -1,233 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.context.propagation.Propagators.defaultPropagator; -import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND; -import static datadog.trace.api.datastreams.DataStreamsTags.create; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCE; -import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; -import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; -import static datadog.trace.instrumentation.jms.MessageInjectAdapter.SETTER; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.annotation.AppliesOn; -import datadog.trace.api.Config; -import datadog.trace.api.datastreams.DataStreamsContext; -import datadog.trace.api.datastreams.DataStreamsTags; -import datadog.trace.bootstrap.CallDepthThreadLocalMap; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; -import javax.jms.Destination; -import javax.jms.Message; -import javax.jms.MessageProducer; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public final class JMSMessageProducerInstrumentation - implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - private final String namespace; - - public JMSMessageProducerInstrumentation(String namespace) { - this.namespace = namespace; - } - - @Override - public String hierarchyMarkerType() { - return namespace + ".jms.MessageProducer"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return implementsInterface(named(hierarchyMarkerType())); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvices( - named("send").and(takesArgument(0, named(namespace + ".jms.Message"))).and(isPublic()), - JMSMessageProducerInstrumentation.class.getName() + "$ProducerAdvice", - JMSMessageProducerInstrumentation.class.getName() + "$ProducerContextPropagationAdvice"); - transformer.applyAdvices( - named("send") - .and(takesArgument(0, hasInterface(named(namespace + ".jms.Destination")))) - .and(takesArgument(1, named(namespace + ".jms.Message"))) - .and(isPublic()), - JMSMessageProducerInstrumentation.class.getName() + "$ProducerWithDestinationAdvice", - JMSMessageProducerInstrumentation.class.getName() - + "$ProducerWithDestinationContextPropagationAdvice"); - } - - public static class ProducerAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static AgentScope beforeSend( - @Advice.Argument(0) final Message message, @Advice.This final MessageProducer producer) { - final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class); - if (callDepth > 0) { - return null; - } - - MessageProducerState producerState = - InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) - .get(producer); - - CharSequence resourceName; - String destinationName; - try { - // fall-back when producer wasn't created via standard Session.createProducer API - if (null != producerState) { - resourceName = producerState.getResourceName(); - Destination destination = PRODUCER_DECORATE.getDestination(producer); - destinationName = PRODUCER_DECORATE.getDestinationName(destination); - } else { - Destination destination = PRODUCER_DECORATE.getDestination(producer); - destinationName = PRODUCER_DECORATE.getDestinationName(destination); - boolean isQueue = PRODUCER_DECORATE.isQueue(destination); - resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); - } - } catch (Exception ignored) { - resourceName = "Unknown Destination"; - destinationName = ""; - } - - final AgentSpan span = startSpan("jms", JMS_PRODUCE); - PRODUCER_DECORATE.afterStart(span); - PRODUCER_DECORATE.onProduce(span, resourceName); - - if (null != destinationName - && !destinationName.isEmpty() - && Config.get().isDataStreamsEnabled()) { - final String tech = messageTechnology(message); - if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ - DataStreamsTags tags = create(tech, OUTBOUND, destinationName); - DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); - } - } - - if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED && null != producerState) { - SETTER.injectTimeInQueue(message, producerState); - } - return activateSpan(span); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void afterSend( - @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { - if (scope == null) { - return; - } - PRODUCER_DECORATE.onError(scope, throwable); - PRODUCER_DECORATE.beforeFinish(scope); - scope.close(); - scope.span().finish(); - CallDepthThreadLocalMap.reset(MessageProducer.class); - } - } - - @AppliesOn(CONTEXT_TRACKING) - public static class ProducerContextPropagationAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) final Message message, @Advice.This final MessageProducer producer) { - AgentSpan span = activeSpan(); - if (span == null) return; - if (JMSDecorator.canInject(message) && Config.get().isJmsPropagationEnabled()) { - MessageProducerState producerState = - InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) - .get(producer); - if (null == producerState || !producerState.isPropagationDisabled()) { - defaultPropagator().inject(span, message, SETTER); - } - } - } - } - - public static class ProducerWithDestinationAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static AgentScope beforeSend( - @Advice.Argument(0) final Destination destination, - @Advice.Argument(1) final Message message, - @Advice.This final MessageProducer producer) { - final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class); - if (callDepth > 0) { - return null; - } - - boolean isQueue = PRODUCER_DECORATE.isQueue(destination); - String destinationName = PRODUCER_DECORATE.getDestinationName(destination); - CharSequence resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); - - final AgentSpan span = startSpan("jms", JMS_PRODUCE); - PRODUCER_DECORATE.afterStart(span); - PRODUCER_DECORATE.onProduce(span, resourceName); - - if (null != destinationName - && !destinationName.isEmpty() - && Config.get().isDataStreamsEnabled()) { - final String tech = messageTechnology(message); - if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ - DataStreamsTags tags = create(tech, OUTBOUND, destinationName); - DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); - } - } - - if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED) { - MessageProducerState producerState = - InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) - .get(producer); - if (null != producerState) { - SETTER.injectTimeInQueue(message, producerState); - } - } - return activateSpan(span); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void afterSend( - @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { - if (scope == null) { - return; - } - PRODUCER_DECORATE.onError(scope, throwable); - PRODUCER_DECORATE.beforeFinish(scope); - scope.close(); - scope.span().finish(); - CallDepthThreadLocalMap.reset(MessageProducer.class); - } - } - - @AppliesOn(CONTEXT_TRACKING) - public static class ProducerWithDestinationContextPropagationAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) final Destination destination, - @Advice.Argument(1) final Message message) { - AgentSpan span = activeSpan(); - if (span == null) return; - if (JMSDecorator.canInject(message) && Config.get().isJmsPropagationEnabled()) { - String destinationName = PRODUCER_DECORATE.getDestinationName(destination); - if (!Config.get().isJmsPropagationDisabledForDestination(destinationName)) { - defaultPropagator().inject(span, message, SETTER); - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java deleted file mode 100644 index 9c0ef70a12b..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java +++ /dev/null @@ -1,63 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; -import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; -import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; -import datadog.trace.bootstrap.instrumentation.jms.SessionState; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@AutoService(InstrumenterModule.class) -public class JavaxJmsModule extends InstrumenterModule.Tracing { - private final String namespace; - - public JavaxJmsModule() { - this("javax", "jms", "jms-1", "jms-2"); - } - - public JavaxJmsModule(String namespace, String instrumentationName, String... additionalNames) { - super(instrumentationName, additionalNames); - this.namespace = namespace; - } - - @Override - public String muzzleDirective() { - return "javax.jms"; - } - - @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".JMSDecorator", - packageName + ".MessageExtractAdapter", - packageName + ".MessageExtractAdapter$1", - packageName + ".MessageInjectAdapter", - packageName + ".DatadogMessageListener", - packageName + ".JMSLogger" - }; - } - - @Override - public Map contextStore() { - Map contextStore = new HashMap<>(4); - contextStore.put(namespace + ".jms.MessageConsumer", MessageConsumerState.class.getName()); - contextStore.put(namespace + ".jms.MessageProducer", MessageProducerState.class.getName()); - contextStore.put(namespace + ".jms.Message", SessionState.class.getName()); - contextStore.put(namespace + ".jms.Session", SessionState.class.getName()); - return contextStore; - } - - @Override - public List typeInstrumentations() { - return Arrays.asList( - new JMSMessageConsumerInstrumentation(namespace), - new JMSMessageProducerInstrumentation(namespace), - new MDBMessageConsumerInstrumentation(namespace), - new MessageInstrumentation(namespace), - new SessionInstrumentation(namespace)); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java deleted file mode 100644 index d9b878f9c69..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java +++ /dev/null @@ -1,115 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.context.propagation.Propagators.defaultPropagator; -import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresAnnotation; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; -import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; -import static datadog.trace.instrumentation.jms.JMSDecorator.logJMSException; -import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.context.ContextScope; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.annotation.AppliesOn; -import datadog.trace.bootstrap.CallDepthThreadLocalMap; -import datadog.trace.bootstrap.instrumentation.api.AgentScope; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageListener; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public final class MDBMessageConsumerInstrumentation - implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - private final String namespace; - - public MDBMessageConsumerInstrumentation(String namespace) { - this.namespace = namespace; - } - - @Override - public String hierarchyMarkerType() { - return namespace + ".jms.MessageListener"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return implementsInterface(named(hierarchyMarkerType())) - .and( - hasSuperType(declaresAnnotation(named(namespace + ".ejb.MessageDriven"))) - .or(implementsInterface(named(namespace + ".ejb.MessageDrivenBean")))); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvices( - isMethod() - .and(isPublic()) - .and(named("onMessage")) - .and(takesArguments(1)) - .and(takesArgument(0, (named(namespace + ".jms.Message")))), - getClass().getName() + "$ContextPropagationAdvice", - getClass().getName() + "$MDBAdvice"); - } - - @AppliesOn(CONTEXT_TRACKING) - public static class ContextPropagationAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) final Message message, @Advice.Local("ctxScope") ContextScope scope) { - scope = defaultPropagator().extract(getRootContext(), message, GETTER).attach(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit(@Advice.Local("ctxScope") ContextScope scope) { - if (scope != null) scope.close(); - } - } - - public static class MDBAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static AgentScope methodEnter(@Advice.Argument(0) final Message message) { - if (CallDepthThreadLocalMap.incrementCallDepth(MessageListener.class) > 0) { - return null; - } - AgentSpan span = startSpan("jms", JMS_CONSUME); - CONSUMER_DECORATE.afterStart(span); - CharSequence consumerResourceName; - try { - Destination destination = message.getJMSDestination(); - boolean isQueue = CONSUMER_DECORATE.isQueue(destination); - String destinationName = CONSUMER_DECORATE.getDestinationName(destination); - consumerResourceName = CONSUMER_DECORATE.toResourceName(destinationName, isQueue); - } catch (JMSException e) { - logJMSException(e); - consumerResourceName = "unknown JMS destination"; - } - CONSUMER_DECORATE.onConsume(span, message, consumerResourceName); - return activateSpan(span); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void methodExit( - @Advice.Enter AgentScope scope, @Advice.Thrown final Throwable throwable) { - if (null != scope) { - CallDepthThreadLocalMap.reset(MessageListener.class); - CONSUMER_DECORATE.onError(scope, throwable); - scope.close(); - scope.span().finish(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java deleted file mode 100644 index 6d233e3c276..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java +++ /dev/null @@ -1,81 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_BATCH_ID_KEY; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCED_KEY; - -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; -import de.thetaphi.forbiddenapis.SuppressForbidden; -import java.util.Enumeration; -import java.util.Locale; -import java.util.function.Function; -import javax.jms.JMSException; -import javax.jms.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class MessageExtractAdapter implements AgentPropagation.ContextVisitor { - private static final Logger log = LoggerFactory.getLogger(MessageExtractAdapter.class); - private static final Function KEY_MAPPER = - new Function() { - @SuppressForbidden - @Override - public String apply(String key) { - return key.replace("__dash__", "-").replace('$', '-').toLowerCase(Locale.ROOT); - } - }; - - private final DDCache cache = DDCaches.newFixedSizeCache(32); - - public static final MessageExtractAdapter GETTER = new MessageExtractAdapter(); - - @Override - public void forEachKey(Message carrier, AgentPropagation.KeyClassifier classifier) { - try { - final Enumeration enumeration = carrier.getPropertyNames(); - if (null != enumeration) { - while (enumeration.hasMoreElements()) { - String key = ((String) enumeration.nextElement()); - String lowerCaseKey = cache.computeIfAbsent(key, KEY_MAPPER); - Object value = null; - try { - value = carrier.getObjectProperty(key); - } catch (Throwable t) { - // log and ignore if we cannot access this property but don't break the instrumentation - if (log.isDebugEnabled()) { - log.debug("Error accessing message property {}", key, t); - } - } - if (value instanceof String && !classifier.accept(lowerCaseKey, (String) value)) { - return; - } - } - } - } catch (JMSException e) { - throw new RuntimeException(e); - } - } - - public long extractTimeInQueueStart(final Message carrier) { - try { - if (carrier.propertyExists(JMS_PRODUCED_KEY)) { - return carrier.getLongProperty(JMS_PRODUCED_KEY); - } - } catch (Exception e) { - log.debug("Unable to get jms produced time", e); - } - return 0; - } - - public long extractMessageBatchId(final Message carrier) { - try { - if (carrier.propertyExists(JMS_BATCH_ID_KEY)) { - return carrier.getLongProperty(JMS_BATCH_ID_KEY); - } - } catch (Exception e) { - log.debug("Unable to get jms batch id", e); - } - return 0; - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java deleted file mode 100644 index 7d2e558fc79..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java +++ /dev/null @@ -1,45 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_BATCH_ID_KEY; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCED_KEY; - -import datadog.context.propagation.CarrierSetter; -import datadog.trace.bootstrap.instrumentation.jms.MessageBatchState; -import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; -import de.thetaphi.forbiddenapis.SuppressForbidden; -import javax.annotation.ParametersAreNonnullByDefault; -import javax.jms.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageInjectAdapter implements CarrierSetter { - private static final Logger log = LoggerFactory.getLogger(MessageInjectAdapter.class); - - public static final MessageInjectAdapter SETTER = new MessageInjectAdapter(); - - @ParametersAreNonnullByDefault - @SuppressForbidden - @Override - public void set(final Message carrier, final String key, final String value) { - final String propName = key.replace("-", "__dash__"); - try { - carrier.setStringProperty(propName, value); - } catch (Exception e) { - log.debug("Failure setting jms property: {}", propName, e); - } - } - - public void injectTimeInQueue(final Message carrier, final MessageProducerState producerState) { - try { - if (producerState.getSessionState().isTransactedSession()) { - MessageBatchState batchState = producerState.currentBatchState(); - carrier.setLongProperty(JMS_BATCH_ID_KEY, batchState.getBatchId()); - carrier.setLongProperty(JMS_PRODUCED_KEY, batchState.getStartMillis()); - } else { - carrier.setLongProperty(JMS_PRODUCED_KEY, System.currentTimeMillis()); - } - } catch (Exception e) { - log.debug("Failure setting jms batch details", e); - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java deleted file mode 100644 index 8bfda025d32..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java +++ /dev/null @@ -1,53 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameStartsWith; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.jms.SessionState; -import javax.jms.Message; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class MessageInstrumentation - implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - private final String namespace; - - public MessageInstrumentation(String namespace) { - this.namespace = namespace; - } - - @Override - public String hierarchyMarkerType() { - return namespace + ".jms.Message"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return implementsInterface(named(hierarchyMarkerType())); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - nameStartsWith("acknowledge").and(isMethod()).and(isPublic()).and(takesNoArguments()), - getClass().getName() + "$Acknowledge"); - } - - public static final class Acknowledge { - @Advice.OnMethodExit - public static void acknowledge(@Advice.This Message message) { - SessionState sessionState = - InstrumentationContext.get(Message.class, SessionState.class).get(message); - if (null != sessionState && sessionState.isClientAcknowledge()) { - sessionState.onAcknowledgeOrRecover(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java deleted file mode 100644 index 8c3ffa48231..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java +++ /dev/null @@ -1,220 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; -import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_LEGACY_TRACING; -import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE; -import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.api.Config; -import datadog.trace.bootstrap.ContextStore; -import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; -import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; -import datadog.trace.bootstrap.instrumentation.jms.SessionState; -import javax.jms.Destination; -import javax.jms.MessageConsumer; -import javax.jms.MessageProducer; -import javax.jms.Session; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class SessionInstrumentation - implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - private final String namespace; - - public SessionInstrumentation(String namespace) { - this.namespace = namespace; - } - - @Override - public String hierarchyMarkerType() { - return namespace + ".jms.Session"; - } - - @Override - public ElementMatcher hierarchyMatcher() { - return implementsInterface(named(hierarchyMarkerType())); - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod() - .and(named("createProducer")) - .and(isPublic()) - .and(takesArgument(0, named(namespace + ".jms.Destination"))), - getClass().getName() + "$CreateProducer"); - transformer.applyAdvice( - isMethod() - .and(named("createSender")) - .and(isPublic()) - .and(takesArgument(0, named(namespace + ".jms.Queue"))), - getClass().getName() + "$CreateProducer"); - transformer.applyAdvice( - isMethod() - .and(named("createPublisher")) - .and(isPublic()) - .and(takesArgument(0, named(namespace + ".jms.Topic"))), - getClass().getName() + "$CreateProducer"); - - transformer.applyAdvice( - isMethod() - .and(named("createConsumer")) - .and(isPublic()) - .and(takesArgument(0, named(namespace + ".jms.Destination"))), - getClass().getName() + "$CreateConsumer"); - transformer.applyAdvice( - isMethod() - .and(named("createReceiver")) - .and(isPublic()) - .and(takesArgument(0, named(namespace + ".jms.Queue"))), - getClass().getName() + "$CreateConsumer"); - transformer.applyAdvice( - isMethod() - .and(namedOneOf("createSubscriber", "createDurableSubscriber")) - .and(isPublic()) - .and(takesArgument(0, named(namespace + ".jms.Topic"))), - getClass().getName() + "$CreateConsumer"); - - transformer.applyAdvice( - namedOneOf("recover").and(takesNoArguments()), getClass().getName() + "$Recover"); - transformer.applyAdvice( - namedOneOf("commit", "rollback").and(takesNoArguments()), getClass().getName() + "$Commit"); - transformer.applyAdvice( - named("close").and(takesNoArguments()), getClass().getName() + "$Close"); - } - - public static final class CreateProducer { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void bindProducerState( - @Advice.This Session session, - @Advice.Argument(0) Destination destination, - @Advice.Return MessageProducer producer) { - - ContextStore producerStateStore = - InstrumentationContext.get(MessageProducer.class, MessageProducerState.class); - - // avoid doing the same thing more than once when there is delegation to overloads - if (producerStateStore.get(producer) == null) { - ContextStore sessionStateStore = - InstrumentationContext.get(Session.class, SessionState.class); - - SessionState sessionState = sessionStateStore.get(session); - if (null == sessionState) { - int ackMode; - try { - ackMode = session.getAcknowledgeMode(); - } catch (Throwable ignored) { - ackMode = Session.AUTO_ACKNOWLEDGE; - } - sessionState = - sessionStateStore.putIfAbsent( - session, new SessionState(ackMode, TIME_IN_QUEUE_ENABLED)); - } - - boolean isQueue = PRODUCER_DECORATE.isQueue(destination); - String destinationName = PRODUCER_DECORATE.getDestinationName(destination); - CharSequence resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); - - boolean propagationDisabled = - Config.get().isJmsPropagationDisabledForDestination(destinationName); - - producerStateStore.put( - producer, new MessageProducerState(sessionState, resourceName, propagationDisabled)); - } - } - } - - public static final class CreateConsumer { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void bindConsumerState( - @Advice.This Session session, - @Advice.Argument(0) Destination destination, - @Advice.Return MessageConsumer consumer) { - - ContextStore consumerStateStore = - InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class); - - // avoid doing the same thing more than once when there is delegation to overloads - if (consumerStateStore.get(consumer) == null) { - ContextStore sessionStateStore = - InstrumentationContext.get(Session.class, SessionState.class); - - SessionState sessionState = sessionStateStore.get(session); - if (null == sessionState) { - int ackMode; - try { - ackMode = session.getAcknowledgeMode(); - } catch (Throwable ignored) { - ackMode = Session.AUTO_ACKNOWLEDGE; - } - sessionState = - sessionStateStore.putIfAbsent( - session, new SessionState(ackMode, TIME_IN_QUEUE_ENABLED)); - } - - boolean isQueue = CONSUMER_DECORATE.isQueue(destination); - String destinationName = CONSUMER_DECORATE.getDestinationName(destination); - CharSequence brokerResourceName = - JMS_LEGACY_TRACING ? "jms" : BROKER_DECORATE.toResourceName(destinationName, isQueue); - CharSequence consumerResourceName = - CONSUMER_DECORATE.toResourceName(destinationName, isQueue); - - boolean propagationDisabled = - Config.get().isJmsPropagationDisabledForDestination(destinationName); - - consumerStateStore.put( - consumer, - new MessageConsumerState( - sessionState, - brokerResourceName, - destinationName, - consumerResourceName, - propagationDisabled)); - } - } - } - - public static final class Recover { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void recover(@Advice.This Session session) { - SessionState sessionState = - InstrumentationContext.get(Session.class, SessionState.class).get(session); - if (null != sessionState && sessionState.isClientAcknowledge()) { - sessionState.onAcknowledgeOrRecover(); - } - } - } - - public static final class Commit { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void commit(@Advice.This Session session) { - SessionState sessionState = - InstrumentationContext.get(Session.class, SessionState.class).get(session); - if (null != sessionState && sessionState.isTransactedSession()) { - sessionState.onCommitOrRollback(); - } - } - } - - public static final class Close { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void close(@Advice.This Session session) { - SessionState sessionState = - InstrumentationContext.get(Session.class, SessionState.class).get(session); - if (null != sessionState) { - sessionState.onClose(); - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy deleted file mode 100644 index f24b7e8f9ff..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy +++ /dev/null @@ -1,1110 +0,0 @@ -import static datadog.trace.api.config.TraceInstrumentationConfig.LEGACY_CONTEXT_MANAGER_ENABLED -import static org.junit.jupiter.api.Assumptions.assumeTrue - -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.agent.test.asserts.TraceAssert -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.api.Config -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.Trace -import datadog.trace.api.config.TraceInstrumentationConfig -import datadog.trace.api.config.TracerConfig -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference -import javax.jms.Connection -import javax.jms.ConnectionFactory -import javax.jms.Destination -import javax.jms.Message -import javax.jms.MessageListener -import javax.jms.Queue -import javax.jms.QueueConnection -import javax.jms.QueueSession -import javax.jms.Session -import javax.jms.TemporaryQueue -import javax.jms.TemporaryTopic -import javax.jms.TextMessage -import javax.jms.Topic -import javax.jms.TopicConnection -import javax.jms.TopicSession -import jms10mock.Jms10ConnectionFactory -import org.apache.activemq.command.ActiveMQTextMessage -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import spock.lang.Shared - -abstract class JMS1Test extends VersionedNamingTestBase { - @Shared - EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() - @Shared - String messageText1 = "a message" - @Shared - String messageText2 = "another message" - @Shared - String messageText3 = "yet another message" - @Shared - Session session - @Shared - QueueSession queueSession - @Shared - TopicSession topicSession - - @Shared - Connection connection - @Shared - QueueConnection queueConnection - @Shared - TopicConnection topicConnection - - ActiveMQTextMessage message1 = session.createTextMessage(messageText1) - ActiveMQTextMessage message2 = session.createTextMessage(messageText2) - ActiveMQTextMessage message3 = session.createTextMessage(messageText3) - - abstract String operationForProducer() - - abstract String operationForConsumer() - - boolean testUnclosedScopeFinished() { - true - } - - def createConnectionFactory() { - broker.createConnectionFactory() - } - - def setupSpec() { - broker.start() - final ConnectionFactory connectionFactory = createConnectionFactory() - - connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - - queueConnection = connectionFactory.createQueueConnection() - queueConnection.start() - queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE) - - topicConnection = connectionFactory.createTopicConnection() - topicConnection.setClientID('gradle') - topicConnection.start() - topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE) - } - - def cleanupSpec() { - broker.stop() - } - - /** - * Create unique destinations of different types. If queue/topic is not temporary, the name will be unique. This - * avoids leaking data between tests. Type is enum to get a sane toString that guarantees stable test IDs. - */ - enum DestinationType { - QUEUE, TOPIC, TEMPORARY_QUEUE, TEMPORARY_TOPIC - - private static int counter = 0 - - Destination create(final Session session) { - switch (this) { - case QUEUE: - return session.createQueue("queue-${counter++}") - case TOPIC: - return session.createTopic("topic-${counter++}") - case TEMPORARY_QUEUE: - return session.createTemporaryQueue() - case TEMPORARY_TOPIC: - return session.createTemporaryTopic() - default: - throw new IllegalArgumentException("Unknown destination type: $this") - } - } - } - - def "sending messages to #destinationType generates spans"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - TextMessage receivedMessage3 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - consumer.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "closing #destinationType session should close and finish any pending scopes"() { - setup: - assumeTrue(testUnclosedScopeFinished()) - def destination = destinationType.create(session) - def localSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = localSession.createProducer(destination) - def consumer = localSession.createConsumer(destination) - - producer.send(message1) - - TextMessage receivedMessage = consumer.receive() - localSession.close() - - expect: - receivedMessage.text == messageText1 - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "receiving messages from #destinationType in a transacted session"() { - setup: - def destination = destinationType.create(session) - def transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = session.createProducer(destination) - def consumer = transactedSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - transactedSession.commit() - TextMessage receivedMessage3 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - transactedSession.commit() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - transactedSession.commit() - producer.close() - consumer.close() - transactedSession.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "receiving messages from #destinationType with manual acknowledgement"() { - setup: - // Use a long scope iteration keep-alive to prevent early cleanup of the 3rd - // consumer span, ensuring exactly 5 traces before acknowledge (not 6). - injectSysConfig(TracerConfig.SCOPE_ITERATION_KEEP_ALIVE, "10000") - def destination = destinationType.create(session) - def clientSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def producer = session.createProducer(destination) - def consumer = clientSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - receivedMessage2.acknowledge() - TextMessage receivedMessage3 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point because message 3 - // has not been acknowledged and the long keep-alive prevents early cleanup - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - receivedMessage3.acknowledge() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - producer.close() - consumer.close() - clientSession.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "recovering messages from #destinationType with manual acknowledgement"() { - setup: - def destination = destinationType.create(session) - def clientSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def producer = session.createProducer(destination) - def consumer = clientSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - clientSession.recover() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - // two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - receivedMessage1 = consumer.receive() - receivedMessage2 = consumer.receive() - TextMessage receivedMessage3 = consumer.receive() - receivedMessage1.acknowledge() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // the two consume traces plus three more will be finished at this point - assertTraces(8) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(0)[0]) // redelivered message - consumerTraceWithNaming(it, destination, trace(1)[0]) // redelivered message - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - receivedMessage3.acknowledge() - producer.close() - consumer.close() - clientSession.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending to a MessageListener on #destinationType generates a span"() { - setup: - def destination = destinationType.create(session) - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - - producer.send(message1) - lock.countDown() - - expect: - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText1 - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending to a null MessageListener on #destinationType generates only producer spans"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener(null) - - producer.send(message1) - - expect: - assertTraces(1) { - producerTraceWithNaming(it, destination) - } - - cleanup: - producer.close() - consumer.receiveNoWait() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "failing to receive message with receiveNoWait on #destinationType works"() { - setup: - def destination = destinationType.create(session) - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receiveNoWait() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - } - - def "failing to receive message with wait(timeout) on #destinationType works"() { - setup: - def destination = destinationType.create(session) - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receive(100) - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - } - - def "sending a read-only message to #destinationType fails"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - expect: - !message1.isReadOnlyProperties() - - when: - message1.setReadOnlyProperties(true) - and: - producer.send(message1) - - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - then: - receivedMessage.text == messageText1 - - // This will result in a logged failure because we tried to - // write properties in MessagePropertyTextMap when readOnlyProperties = true. - // The consumer span will also not be linked to the parent. - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - // Consumer trace - span { - parent() - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending a message with disabled timestamp generates spans without specific tag"() { - setup: - def destination = DestinationType.QUEUE.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - producer.setDisableMessageTimestamp(true) - producer.send(message1) - - boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() - consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0], isTimeStampDisabled) - } - - cleanup: - producer.close() - consumer.close() - } - - def "traceable work between two #destinationType receive calls has jms.consume parent"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - producer.send(message1) - - TextMessage receivedMessage = consumer.receive() - doStuff() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage.text == messageText1 - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(2) { - consumerSpan(it, toJmsResourceName(destination), trace(0)[0], false, service(), operationForConsumer()) - span { - operationName "do.stuff" - childOf(trace(1)[0]) - } - } - } - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending a message to #destinationType with given disabled topic or queue disables propagation on producer side"() { - setup: - def destination = destinationType.create(session) - // create consumer while propagation is enabled (state will be cached) - def consumer = session.createConsumer(destination) - // now disable propagation for any messages produced in the given topic/queue - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) - def producer = session.createProducer(destination) - producer.send(message1) - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - expect: - receivedMessage.text == messageText1 - if (expected) { - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - } else { - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - span { - parentSpanId(0 as BigInteger) - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - } - cleanup: - producer.close() - consumer.close() - - where: - destinationType | expected - DestinationType.QUEUE | false - DestinationType.TOPIC | false - DestinationType.TEMPORARY_QUEUE | true - DestinationType.TEMPORARY_TOPIC | true - } - - def "sending a message to #destinationType with given disabled topic or queue disables propagation on consumer side"() { - setup: - def destination = destinationType.create(session) - // create consumer while propagation is disabled for given topic/queue (state will be cached) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) - def consumer = session.createConsumer(destination) - // now enable propagation for the producer and any messages produced - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS) - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES) - def producer = session.createProducer(destination) - producer.send(message1) - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - expect: - receivedMessage.text == messageText1 - if (expected) { - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - } else { - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - span { - parentSpanId(0 as BigInteger) - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - } - cleanup: - producer.close() - consumer.close() - - where: - destinationType | expected - DestinationType.QUEUE | false - DestinationType.TOPIC | false - DestinationType.TEMPORARY_QUEUE | true - DestinationType.TEMPORARY_TOPIC | true - } - - def "sending a message to #destinationType with given disabled topic or queue disables propagation in listener"() { - setup: - def destination = destinationType.create(session) - // create consumer while propagation is disabled for given topic/queue (state will be cached) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) - def consumer = session.createConsumer(destination) - // now enable propagation for the producer and any messages produced - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS) - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES) - def producer = session.createProducer(destination) - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - producer.send(message1) - lock.countDown() - - expect: - if (expected) { - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - } else { - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - span { - parentSpanId(0 as BigInteger) - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText1 - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | expected - DestinationType.QUEUE | false - DestinationType.TOPIC | false - DestinationType.TEMPORARY_QUEUE | true - DestinationType.TEMPORARY_TOPIC | true - } - - def "queue session with #destinationType generates spans"() { - setup: - def destination = destinationType.create(queueSession) - def sender = queueSession.createSender(destination) - def receiver = queueSession.createReceiver(destination) - - when: - sender.send(message1) - sender.send(destination, message2) - sender.send(message3) - - TextMessage receivedMessage1 = receiver.receive() - TextMessage receivedMessage2 = receiver.receive() - TextMessage receivedMessage3 = receiver.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - receiver.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - sender.close() - receiver.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TEMPORARY_QUEUE | _ - } - - def "topic session with #destinationType generates spans"() { - setup: - def destination = destinationType.create(topicSession) - def publisher = topicSession.createPublisher(destination) - def subscriber = topicSession.createSubscriber(destination) - - when: - publisher.send(message1) - publisher.send(message2) - publisher.send(message3) - - TextMessage receivedMessage1 = subscriber.receive() - TextMessage receivedMessage2 = subscriber.receive() - TextMessage receivedMessage3 = subscriber.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - subscriber.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - publisher.close() - subscriber.close() - - where: - destinationType | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "durable topic session generates spans"() { - setup: - def destination = DestinationType.TOPIC.create(topicSession) - def publisher = topicSession.createPublisher(destination) - def subscriber = topicSession.createDurableSubscriber(destination, 'test') - - when: - publisher.send(message1) - publisher.send(message2) - publisher.send(message3) - - TextMessage receivedMessage1 = subscriber.receive() - TextMessage receivedMessage2 = subscriber.receive() - TextMessage receivedMessage3 = subscriber.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - subscriber.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - publisher.close() - subscriber.close() - } - - String toJmsResourceName(Destination destination) { - if (destination instanceof TemporaryQueue) { - return "Temporary Queue" - } else if (destination instanceof TemporaryTopic) { - return "Temporary Topic" - } else if (destination instanceof Queue) { - return "Queue ${((Queue) destination).getQueueName()}" - } else if (destination instanceof Topic) { - return "Topic ${((Topic) destination).getTopicName()}" - } - throw new IllegalArgumentException("Unknown destination type: $destination") - } - - String toName(Destination destination) { - if (destination instanceof TemporaryQueue) { - return "" - } else if (destination instanceof TemporaryTopic) { - return "" - } else if (destination instanceof Queue) { - return ((Queue) destination).getQueueName() - } else if (destination instanceof Topic) { - return ((Topic) destination).getTopicName() - } - throw new IllegalArgumentException("Unknown destination type: $destination") - } - - def producerTraceWithNaming(ListWriterAssert writer, Destination destination) { - producerTrace(writer, toJmsResourceName(destination), service(), operationForProducer()) - } - - static producerTrace(ListWriterAssert writer, String jmsResourceName, String producerService = "jms", String producerOperation = "jms.produce") { - writer.trace(1) { - producerSpan(it, jmsResourceName, producerService, producerOperation) - } - } - - static producerSpan(TraceAssert traceAssert, String jmsResourceName, String producerService = "jms", String producerOperation = "jms.produce") { - return traceAssert.span { - serviceName producerService - operationName producerOperation - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - measured true - parent() - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - defaultTagsNoPeerService() - } - } - } - - def consumerTraceWithNaming(ListWriterAssert writer, Destination destination, DDSpan parentSpan, boolean isTimestampDisabled = false) { - consumerTrace(writer, toJmsResourceName(destination), parentSpan, isTimestampDisabled, service(), operationForConsumer()) - } - - static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false, - String consumerService = "jms", String consumerOperation = "jms.consume") { - writer.trace(1) { - consumerSpan(it, jmsResourceName, parentSpan, isTimestampDisabled, consumerService, consumerOperation) - } - } - - static consumerSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false, - String consumerService = "jms", String consumerOperation = "jms.consume") { - return traceAssert.span { - serviceName consumerService - operationName consumerOperation - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - measured true - childOf parentSpan - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - } - defaultTagsNoPeerService(true) - } - } - } - - @Trace(operationName = "do.stuff") - def doStuff() { - } -} - -class JMS1V0Test extends JMS1Test { - - @Override - int version() { - 0 - } - - @Override - String service() { - "jms" - } - - @Override - String operation() { - null - } - - @Override - String operationForProducer() { - "jms.produce" - } - - @Override - String operationForConsumer() { - "jms.consume" - } -} - -class JMSContextSwapForkedTest extends JMS1V0Test { - @Override - protected void configurePreAgent() { - injectSysConfig(LEGACY_CONTEXT_MANAGER_ENABLED, "false") - } - - @Override - boolean testUnclosedScopeFinished() { - //TODO: This need to be removed when the Context manager will support it - false - } -} - -class JMS1V1ForkedTest extends JMS1Test { - - @Override - int version() { - 1 - } - - @Override - String service() { - Config.get().getServiceName() - } - - @Override - String operation() { - null - } - - @Override - String operationForProducer() { - "jms.send" - } - - @Override - String operationForConsumer() { - "jms.process" - } -} - -class JMS10Test extends JMS1V0Test { - @Override - def createConnectionFactory() { - new Jms10ConnectionFactory(super.createConnectionFactory()) - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy deleted file mode 100644 index 1c8f2dc0bdd..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy +++ /dev/null @@ -1,99 +0,0 @@ -import datadog.trace.instrumentation.jms.JMSDecorator -import spock.lang.Specification - -import javax.jms.Queue -import javax.jms.Topic - -class JMSDecoratorTest extends Specification { - - def "test getDestinationName sanitizes Kafka Connect schema suffixes"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def queue = Mock(Queue) { - getQueueName() >> rawQueueName - } - def result = decorator.getDestinationName(queue) - - then: - result == expectedName - - where: - rawQueueName | expectedName - // Customer reported issue: queue name with _messagebody_0 suffix from Kafka Connect IBM MQ connector - // See Zendesk ticket #2429181 - "trainmgt.dispatch.trnsheet.p30.v1.pub_messagebody_0" | "trainmgt.dispatch.trnsheet.p30.v1.pub" - - // Normal queue names should pass through unchanged (like customer's working pure Java apps) - "ee.wo.aei.delmove.cs" | "ee.wo.aei.delmove.cs" - "myqueue" | "myqueue" - "my.queue.name" | "my.queue.name" - - // Other Kafka Connect schema-derived suffixes should also be stripped - "myqueue_messagebody_0" | "myqueue" - "myqueue_text_0" | "myqueue" - "myqueue_bytes_0" | "myqueue" - "myqueue_map_0" | "myqueue" - "myqueue_value_0" | "myqueue" - "myqueue_MESSAGEBODY_0" | "myqueue" // case insensitive - "myqueue_MessageBody_0" | "myqueue" // case insensitive - - // Multiple digit indices - "myqueue_messagebody_10" | "myqueue" - "myqueue_messagebody_123" | "myqueue" - - // Names that look similar but shouldn't be stripped - "myqueue_messagebody" | "myqueue_messagebody" // no index - "messagebody_0_queue" | "messagebody_0_queue" // not at end - "myqueue_othersuffix_0" | "myqueue_othersuffix_0" // unknown suffix - } - - def "test getDestinationName with topic sanitizes Kafka Connect schema suffixes"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def topic = Mock(Topic) { - getTopicName() >> rawTopicName - } - def result = decorator.getDestinationName(topic) - - then: - result == expectedName - - where: - rawTopicName | expectedName - "mytopic" | "mytopic" - "mytopic_messagebody_0" | "mytopic" - "mytopic_text_0" | "mytopic" - } - - def "test getDestinationName returns null for null queue name"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def queue = Mock(Queue) { - getQueueName() >> null - } - def result = decorator.getDestinationName(queue) - - then: - result == null - } - - def "test getDestinationName returns null for TIBCO temp prefix"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def queue = Mock(Queue) { - getQueueName() >> '$TMP$myqueue' - } - def result = decorator.getDestinationName(queue) - - then: - result == null - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy deleted file mode 100644 index dc5b8b20657..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy +++ /dev/null @@ -1,16 +0,0 @@ -import javax.ejb.MessageDrivenBean -import javax.ejb.MessageDrivenContext -import javax.jms.Message -import javax.jms.MessageListener - -class MDB1 implements MessageDrivenBean, MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } - void ejbRemove() {} - void setMessageDrivenContext(MessageDrivenContext ctx) {} -} - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy deleted file mode 100644 index f748669831a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import javax.ejb.MessageDriven -import javax.jms.Message -import javax.jms.MessageListener - -@MessageDriven(mappedName="unusedTopic") -class MDB2 implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy deleted file mode 100644 index f6f1004befd..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import javax.jms.Message -import javax.jms.MessageListener - -// Not a valid MDB because it only implements MessageListener. -class MDBBad implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy deleted file mode 100644 index eeb7cef9a12..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy +++ /dev/null @@ -1,224 +0,0 @@ -import javax.jms.Message -import javax.jms.Destination -import javax.jms.JMSException - -class MDBJmsMsg implements Message { - - long getJMSDeliveryTime() { - return 1L - } - - void setJMSDeliveryTime(long time) {} - - Object getBody(Class aClass) { - return null - } - - boolean isBodyAssignableTo(Class aClass) { - return false - } - - @Override - String getJMSMessageID() throws JMSException { - return "123" - } - - @Override - void setJMSMessageID(String id) throws JMSException {} - - @Override - long getJMSTimestamp() throws JMSException { - return 0 - } - - @Override - void setJMSTimestamp(long timestamp) throws JMSException { - } - - @Override - byte[] getJMSCorrelationIDAsBytes() throws JMSException { - return new byte[0] - } - - @Override - void setJMSCorrelationIDAsBytes(byte[] correlationID) throws JMSException { - } - - @Override - void setJMSCorrelationID(String correlationID) throws JMSException { - } - - @Override - String getJMSCorrelationID() throws JMSException { - return null - } - - @Override - Destination getJMSReplyTo() throws JMSException { - return null - } - - @Override - void setJMSReplyTo(Destination replyTo) throws JMSException { - } - - @Override - Destination getJMSDestination() throws JMSException { - return null - } - - @Override - void setJMSDestination(Destination destination) throws JMSException { - } - - @Override - int getJMSDeliveryMode() throws JMSException { - return 0 - } - - @Override - void setJMSDeliveryMode(int deliveryMode) throws JMSException { - } - - @Override - boolean getJMSRedelivered() throws JMSException { - return false - } - - @Override - void setJMSRedelivered(boolean redelivered) throws JMSException { - } - - @Override - String getJMSType() throws JMSException { - return null - } - - @Override - void setJMSType(String type) throws JMSException { - } - - @Override - long getJMSExpiration() throws JMSException { - return 0 - } - - @Override - void setJMSExpiration(long expiration) throws JMSException { - } - - @Override - int getJMSPriority() throws JMSException { - return 0 - } - - @Override - void setJMSPriority(int priority) throws JMSException { - } - - @Override - void clearProperties() throws JMSException { - } - - @Override - boolean propertyExists(String name) throws JMSException { - return false - } - - @Override - boolean getBooleanProperty(String name) throws JMSException { - return false - } - - @Override - byte getByteProperty(String name) throws JMSException { - return 0 - } - - @Override - short getShortProperty(String name) throws JMSException { - return 0 - } - - @Override - int getIntProperty(String name) throws JMSException { - return 0 - } - - @Override - long getLongProperty(String name) throws JMSException { - return 0 - } - - @Override - float getFloatProperty(String name) throws JMSException { - return 0 - } - - @Override - double getDoubleProperty(String name) throws JMSException { - return 0 - } - - @Override - String getStringProperty(String name) throws JMSException { - return null - } - - @Override - Object getObjectProperty(String name) throws JMSException { - return null - } - - @Override - Enumeration getPropertyNames() throws JMSException { - return null - } - - @Override - void setBooleanProperty(String name, boolean value) throws JMSException { - } - - @Override - void setByteProperty(String name, byte value) throws JMSException { - } - - @Override - void setShortProperty(String name, short value) throws JMSException { - } - - @Override - void setIntProperty(String name, int value) throws JMSException { - } - - @Override - void setLongProperty(String name, long value) throws JMSException { - } - - @Override - void setFloatProperty(String name, float value) throws JMSException { - } - - @Override - void setDoubleProperty(String name, double value) throws JMSException { - } - - @Override - void setStringProperty(String name, String value) throws JMSException { - } - - @Override - void setObjectProperty(String name, Object value) throws JMSException { - } - - @Override - void acknowledge() throws JMSException { - } - - @Override - void clearBody() throws JMSException { - } -} - - - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy deleted file mode 100644 index 8baadc802a3..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy +++ /dev/null @@ -1,78 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.agent.test.asserts.ListWriterAssert -import spock.lang.Shared - -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -class MDBTest extends InstrumentationSpecification { - - @Shared - def msg = new MDBJmsMsg() - - def "Test an incomplete MDB that should not get traced here"() { - setup: - def beanBad = new MDBBad() - - when: - runUnderTrace("parent") { - beanBad.onMessage(msg) - } - - then: - assertTraces(1) { - workerTrace(it) - } - } - - def "Test MDB1"() { - setup: - def bean1 = new MDB1() - - when: - runUnderTrace("parent") { - bean1.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def "Test MDB2"() { - setup: - def bean2 = new MDB2() - - when: - runUnderTrace("parent") { - bean2.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def workerTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - serviceName "worker.org.gradle.process.internal.worker.GradleWorkerMain" - } - } - } - - def jmsTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - spanType "queue" - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from Temporary Queue" - measured true - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy deleted file mode 100644 index 5c8e4f259e8..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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. - */ - - -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.test.util.Flaky -import listener.Config -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.jms.core.JmsTemplate - -import javax.jms.ConnectionFactory - -import static JMS1Test.consumerTrace -import static JMS1Test.producerTrace - -@Flaky -class SpringListenerJMS1Test extends InstrumentationSpecification { - - def "receiving message in spring listener generates spans"() { - setup: - def context = new AnnotationConfigApplicationContext(Config) - def factory = context.getBean(ConnectionFactory) - def template = new JmsTemplate(factory) - template.convertAndSend("SpringListenerJMS1", "a message") - - expect: - assertTraces(2) { - producerTrace(it, "Queue SpringListenerJMS1") - consumerTrace(it, "Queue SpringListenerJMS1", trace(0)[0]) - } - - cleanup: - context.getBean(EmbeddedActiveMQBroker).stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy deleted file mode 100644 index 66a0f229d5d..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy +++ /dev/null @@ -1,90 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.test.util.Flaky -import org.apache.activemq.ActiveMQConnectionFactory -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import org.springframework.jms.core.JmsTemplate -import spock.lang.Shared - -import javax.jms.Connection -import javax.jms.Session -import javax.jms.TextMessage -import java.util.concurrent.TimeUnit - -import static JMS1Test.consumerTrace -import static JMS1Test.producerTrace - -class SpringTemplateJMS1Test extends InstrumentationSpecification { - @Shared - EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() - @Shared - JmsTemplate template - @Shared - Session session - - def setupSpec() { - broker.start() - final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() - final Connection connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - - template = new JmsTemplate(connectionFactory) - // Make this longer than timeout on TEST_WRITER.waitForTraces - // Otherwise caller might give up waiting before callee has a chance to respond. - template.receiveTimeout = TimeUnit.SECONDS.toMillis(21) - } - - def cleanupSpec() { - broker.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - template.convertAndSend(destination, messageText) - TextMessage receivedMessage = template.receive(destination) - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS1") | "Queue SpringTemplateJMS1" - - messageText = "a message" - } - - @Flaky("Sometimes fails when finding errors in traces: Cannot publish to a deleted Destination: temp-queue://...") - def "send and receive message generates spans"() { - setup: - Thread.start { - TextMessage msg = template.receive(destination) - assert msg.text == messageText - - template.send(msg.getJMSReplyTo()) { session -> - template.getMessageConverter().toMessage("responded!", session) - } - } - TextMessage receivedMessage = template.sendAndReceive(destination) { session -> - template.getMessageConverter().toMessage(messageText, session) - } - - expect: - receivedMessage.text == "responded!" - assertTraces(4) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root - consumerTrace(it, "Temporary Queue", trace(2)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS1") | "Queue SpringTemplateJMS1" - - messageText = "a message" - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy deleted file mode 100644 index 15e1aa4edb7..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy +++ /dev/null @@ -1,728 +0,0 @@ -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.agent.test.asserts.TraceAssert -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.config.GeneralConfig -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import spock.lang.Shared - -import javax.jms.Connection -import javax.jms.Session -import javax.jms.TextMessage - -abstract class TimeInQueueForkedTestBase extends VersionedNamingTestBase { - @Shared - EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() - @Shared - Connection connection - @Shared - Session session - @Shared - String messageText1 = "a message" - @Shared - String messageText2 = "another message" - @Shared - String messageText3 = "yet another message" - @Shared - String messageText4 = "another message again" - @Shared - String messageText5 = "just one more message" - - TextMessage message1 = session.createTextMessage(messageText1) - TextMessage message2 = session.createTextMessage(messageText2) - TextMessage message3 = session.createTextMessage(messageText3) - TextMessage message4 = session.createTextMessage(messageText4) - TextMessage message5 = session.createTextMessage(messageText5) - - @Override - String operation() { - null - } - - @Override - int version() { - 0 - } - - @Override - String service() { - "myService" - } - - String operationForProducer() { - "jms.produce" - } - - String operationForConsumer() { - "jms.consume" - } - - String serviceForTimeInQueue() { - "jms" - } - - - @Override - protected void configurePreAgent() { - super.configurePreAgent() - - // test explicit only on v0 since we're also testing that in v1 is implicit - if (version() == 0) { - injectSysConfig("jms.legacy.tracing.enabled", 'false') - } - injectSysConfig(GeneralConfig.SERVICE_NAME, 'myService') - } - - abstract boolean splitByDestination() - - def setupSpec() { - broker.start() - connection = broker.createConnectionFactory().createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - } - - def cleanupSpec() { - broker.stop() - } - - def "sending messages to #jmsResourceName generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producer.send(message4) - producer.send(message5) - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only four consume traces will be finished at this point - assertTraces(9, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - consumerTrace(it, jmsResourceName, trace(1)[0]) - consumerTrace(it, jmsResourceName, trace(2)[0]) - consumerTrace(it, jmsResourceName, trace(3)[0]) - } - - when: - consumer.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(10, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - consumerTrace(it, jmsResourceName, trace(1)[0]) - consumerTrace(it, jmsResourceName, trace(2)[0]) - consumerTrace(it, jmsResourceName, trace(3)[0]) - consumerTrace(it, jmsResourceName, trace(4)[0]) - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with manual acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producer.send(message4) - producer.send(message5) - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - receivedMessage2.acknowledge() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only three consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - receivedMessage5.acknowledge() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with listener acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - consumer.setMessageListener { it.acknowledge() } - producer.send(message1) - - then: - assertTraces(2, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(1)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with transacted acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producer.send(message4) - producer.send(message5) - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - consumerSession.commit() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only two consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - consumerSession.commit() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with listener commit generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def consumer = consumerSession.createConsumer(destination) - - when: - consumer.setMessageListener { consumerSession.commit() } - producer.send(message1) - - then: - assertTraces(2, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(1)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending batch messages to #jmsResourceName generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producerSession.commit() - producer.send(message3) - producerSession.commit() - producer.send(message4) - producer.send(message5) - producerSession.commit() - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only four consume traces will be finished at this point - assertTraces(9, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(1) { - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(7)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(8)[0], false) - } - } - - when: - consumer.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(10, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(1) { - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(7)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(8)[0], false) - } - trace(1) { - consumerSpan(it, jmsResourceName, trace(8)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending batch messages to #jmsResourceName with manual acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producerSession.commit() - producer.send(message3) - producer.send(message4) - producer.send(message5) - producerSession.commit() - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - receivedMessage2.acknowledge() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only three consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - receivedMessage5.acknowledge() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending batch messages to #jmsResourceName with transacted acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producerSession.commit() - producer.send(message4) - producer.send(message5) - producerSession.commit() - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - consumerSession.commit() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only two consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - consumerSession.commit() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def producerTrace(ListWriterAssert writer, String jmsResourceName) { - writer.trace(1) { - producerSpan(it, jmsResourceName) - } - } - - def producerSpan(TraceAssert traceAssert, String jmsResourceName) { - return traceAssert.span { - serviceName service() - operationName operationForProducer() - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - measured true - parent() - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - // when not using the legacy tracing the service is set to DD_SERVICE - // while it should just not be set at all. - serviceNameSource "jms" - defaultTagsNoPeerService() - } - } - } - - def consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - writer.trace(2) { - timeInQueueSpan(it, jmsResourceName, parentSpan) - consumerSpan(it, jmsResourceName, span(0), isTimestampDisabled) - } - } - - def consumerSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - return traceAssert.span { - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - measured true - childOf parentSpan - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - } - // when not using the legacy tracing the service is set to DD_SERVICE - // while it should just not be set at all. - serviceNameSource "jms" - defaultTags(false) - } - } - } - - def timeInQueueSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan) { - return traceAssert.span { - serviceName splitByDestination() ? "${jmsResourceName.replaceFirst(/(Queue |Topic )/, '')}" : serviceForTimeInQueue() - operationName "jms.deliver" - resourceName "$jmsResourceName" - spanType DDSpanTypes.MESSAGE_BROKER - errored false - childOf parentSpan - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_BROKER - defaultTagsNoPeerService(true) - } - } - } -} - -class TimeInQueueForkedTest extends TimeInQueueForkedTestBase { - @Override - boolean splitByDestination() { - return false - } -} - -class TimeInQueueSplitByDestinationForkedTest extends TimeInQueueForkedTestBase { - @Override - void configurePreAgent() { - super.configurePreAgent() - injectSysConfig("dd.message.broker.split-by-destination", "true") - } - - @Override - boolean splitByDestination() { - return true - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy deleted file mode 100644 index 881ba0ed80a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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 listener - -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.jms.annotation.EnableJms -import org.springframework.jms.config.DefaultJmsListenerContainerFactory -import org.springframework.jms.config.JmsListenerContainerFactory - -import javax.annotation.PreDestroy -import javax.jms.ConnectionFactory - -@Configuration -@ComponentScan -@EnableJms -class Config { - - @Bean - EmbeddedActiveMQBroker broker() { - def broker = new EmbeddedActiveMQBroker() - broker.start() - return broker - } - - @Bean - ConnectionFactory connectionFactory(EmbeddedActiveMQBroker broker) { - return broker.createConnectionFactory() - } - - @Bean - JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() - factory.setConnectionFactory(connectionFactory) - return factory - } - - @PreDestroy - void destroy() { - broker().stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy deleted file mode 100644 index b537d7614f2..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry 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 listener - -import org.springframework.jms.annotation.JmsListener -import org.springframework.stereotype.Component - -@Component -class TestListener { - - @JmsListener(destination = "SpringListenerJMS1", containerFactory = "containerFactory") - void receiveMessage(String message) { - println "received: " + message - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java deleted file mode 100644 index 0f8721a2b89..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java +++ /dev/null @@ -1,130 +0,0 @@ -package jms10mock; - -import javax.jms.Connection; -import javax.jms.ConnectionConsumer; -import javax.jms.ConnectionMetaData; -import javax.jms.Destination; -import javax.jms.ExceptionListener; -import javax.jms.JMSException; -import javax.jms.Queue; -import javax.jms.QueueConnection; -import javax.jms.QueueSession; -import javax.jms.ServerSessionPool; -import javax.jms.Session; -import javax.jms.Topic; -import javax.jms.TopicConnection; -import javax.jms.TopicSession; - -/** Wraps a real {@link Connection} but simulates a JMS 1.0 provider. */ -public class Jms10Connection implements QueueConnection, TopicConnection { - private final Connection delegate; - - public Jms10Connection(Connection delegate) { - this.delegate = delegate; - } - - // --- JMS 1.1-only unified Connection method --- - - @Override - public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { - throw new AbstractMethodError( - "JMS 1.0 provider does not implement createSession(boolean, int) on Connection"); - } - - // --- JMS 1.0 QueueConnection methods --- - - @Override - public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) - throws JMSException { - return new Jms10Session(delegate.createSession(transacted, acknowledgeMode)); - } - - // --- JMS 1.0 TopicConnection methods --- - - @Override - public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) - throws JMSException { - return new Jms10Session(delegate.createSession(transacted, acknowledgeMode)); - } - - // --- Common Connection methods --- - - @Override - public String getClientID() throws JMSException { - return delegate.getClientID(); - } - - @Override - public void setClientID(String clientID) throws JMSException { - delegate.setClientID(clientID); - } - - @Override - public ConnectionMetaData getMetaData() throws JMSException { - return delegate.getMetaData(); - } - - @Override - public ExceptionListener getExceptionListener() throws JMSException { - return delegate.getExceptionListener(); - } - - @Override - public void setExceptionListener(ExceptionListener listener) throws JMSException { - delegate.setExceptionListener(listener); - } - - @Override - public void start() throws JMSException { - delegate.start(); - } - - @Override - public void stop() throws JMSException { - delegate.stop(); - } - - @Override - public void close() throws JMSException { - delegate.close(); - } - - // --- ConnectionConsumer methods — not commonly used, throw for JMS 1.1 unified form --- - - @Override - public ConnectionConsumer createConnectionConsumer( - Destination destination, - String messageSelector, - ServerSessionPool sessionPool, - int maxMessages) - throws JMSException { - throw new AbstractMethodError( - "JMS 1.0 provider does not implement createConnectionConsumer(Destination, ...)"); - } - - @Override - public ConnectionConsumer createConnectionConsumer( - Queue queue, String messageSelector, ServerSessionPool sessionPool, int maxMessages) - throws JMSException { - return delegate.createConnectionConsumer(queue, messageSelector, sessionPool, maxMessages); - } - - @Override - public ConnectionConsumer createConnectionConsumer( - Topic topic, String messageSelector, ServerSessionPool sessionPool, int maxMessages) - throws JMSException { - return delegate.createConnectionConsumer(topic, messageSelector, sessionPool, maxMessages); - } - - @Override - public ConnectionConsumer createDurableConnectionConsumer( - Topic topic, - String subscriptionName, - String messageSelector, - ServerSessionPool sessionPool, - int maxMessages) - throws JMSException { - return delegate.createDurableConnectionConsumer( - topic, subscriptionName, messageSelector, sessionPool, maxMessages); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java deleted file mode 100644 index 1660765f731..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -package jms10mock; - -import javax.jms.Connection; -import javax.jms.ConnectionFactory; -import javax.jms.JMSException; -import javax.jms.QueueConnection; -import javax.jms.QueueConnectionFactory; -import javax.jms.TopicConnection; -import javax.jms.TopicConnectionFactory; - -/** - * Wraps a real {@link ConnectionFactory} but simulates a JMS 1.0 provider. - * - *

In JMS 1.0, clients used the domain-specific {@link QueueConnectionFactory} and {@link - * TopicConnectionFactory} to obtain connections. The unified {@link ConnectionFactory} and its - * {@code createConnection()} methods are JMS 1.1 additions that this wrapper does not support. - */ -public class Jms10ConnectionFactory implements QueueConnectionFactory, TopicConnectionFactory { - private final ConnectionFactory delegate; - - public Jms10ConnectionFactory(ConnectionFactory delegate) { - this.delegate = delegate; - } - - // --- JMS 1.1-only unified ConnectionFactory methods --- - - @Override - public Connection createConnection() throws JMSException { - return delegate.createConnection(); - } - - @Override - public Connection createConnection(String userName, String password) throws JMSException { - return delegate.createConnection(userName, password); - } - - // --- JMS 1.0 QueueConnectionFactory methods --- - @Override - public QueueConnection createQueueConnection() throws JMSException { - return new Jms10Connection(delegate.createConnection()); - } - - @Override - public QueueConnection createQueueConnection(String userName, String password) - throws JMSException { - return new Jms10Connection(delegate.createConnection(userName, password)); - } - - // --- JMS 1.0 TopicConnectionFactory methods --- - - @Override - public TopicConnection createTopicConnection() throws JMSException { - return new Jms10Connection(delegate.createConnection()); - } - - @Override - public TopicConnection createTopicConnection(String userName, String password) - throws JMSException { - return new Jms10Connection(delegate.createConnection(userName, password)); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java deleted file mode 100644 index 92b8f8ec93b..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java +++ /dev/null @@ -1,59 +0,0 @@ -package jms10mock; - -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageListener; -import javax.jms.Queue; -import javax.jms.QueueReceiver; - -/** Wraps a real {@link MessageConsumer} but simulates a JMS 1.0 provider. */ -public class Jms10QueueReceiver implements QueueReceiver { - private final MessageConsumer delegate; - private final Queue queue; - - public Jms10QueueReceiver(MessageConsumer delegate, Queue queue) { - this.delegate = delegate; - this.queue = queue; - } - - @Override - public Queue getQueue() { - return queue; - } - - @Override - public String getMessageSelector() throws JMSException { - return delegate.getMessageSelector(); - } - - @Override - public MessageListener getMessageListener() throws JMSException { - return delegate.getMessageListener(); - } - - @Override - public void setMessageListener(MessageListener listener) throws JMSException { - delegate.setMessageListener(listener); - } - - @Override - public Message receive() throws JMSException { - return delegate.receive(); - } - - @Override - public Message receive(long timeout) throws JMSException { - return delegate.receive(timeout); - } - - @Override - public Message receiveNoWait() throws JMSException { - return delegate.receiveNoWait(); - } - - @Override - public void close() throws JMSException { - delegate.close(); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java deleted file mode 100644 index 1f888203483..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java +++ /dev/null @@ -1,124 +0,0 @@ -package jms10mock; - -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageProducer; -import javax.jms.Queue; -import javax.jms.QueueSender; - -/** Wraps a real {@link MessageProducer} but simulates a JMS 1.0 provider. */ -public class Jms10QueueSender implements QueueSender { - private final MessageProducer delegate; - private final Queue queue; - - public Jms10QueueSender(MessageProducer delegate, Queue queue) { - this.delegate = delegate; - this.queue = queue; - } - - // --- JMS 1.1-only methods — not present in JMS 1.0 --- - - @Override - public Destination getDestination() { - throw new AbstractMethodError("JMS 1.0 provider does not implement getDestination()"); - } - - @Override - public void send(Destination destination, Message message) throws JMSException { - delegate.send(destination, message); - } - - @Override - public void send( - Destination destination, Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(destination, message, deliveryMode, priority, timeToLive); - } - - // --- JMS 1.0 QueueSender methods --- - - @Override - public Queue getQueue() { - return queue; - } - - @Override - public void send(Message message) throws JMSException { - delegate.send(message); - } - - @Override - public void send(Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(message, deliveryMode, priority, timeToLive); - } - - @Override - public void send(Queue queue, Message message) throws JMSException { - delegate.send(queue, message); - } - - @Override - public void send(Queue queue, Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(queue, message, deliveryMode, priority, timeToLive); - } - - // --- MessageProducer config methods --- - - @Override - public void close() throws JMSException { - delegate.close(); - } - - @Override - public void setDisableMessageID(boolean value) throws JMSException { - delegate.setDisableMessageID(value); - } - - @Override - public boolean getDisableMessageID() throws JMSException { - return delegate.getDisableMessageID(); - } - - @Override - public void setDisableMessageTimestamp(boolean value) throws JMSException { - delegate.setDisableMessageTimestamp(value); - } - - @Override - public boolean getDisableMessageTimestamp() throws JMSException { - return delegate.getDisableMessageTimestamp(); - } - - @Override - public void setDeliveryMode(int deliveryMode) throws JMSException { - delegate.setDeliveryMode(deliveryMode); - } - - @Override - public int getDeliveryMode() throws JMSException { - return delegate.getDeliveryMode(); - } - - @Override - public void setPriority(int defaultPriority) throws JMSException { - delegate.setPriority(defaultPriority); - } - - @Override - public int getPriority() throws JMSException { - return delegate.getPriority(); - } - - @Override - public void setTimeToLive(long timeToLive) throws JMSException { - delegate.setTimeToLive(timeToLive); - } - - @Override - public long getTimeToLive() throws JMSException { - return delegate.getTimeToLive(); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java deleted file mode 100644 index 65629319237..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java +++ /dev/null @@ -1,229 +0,0 @@ -package jms10mock; - -import java.io.Serializable; -import javax.jms.BytesMessage; -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.MapMessage; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageListener; -import javax.jms.MessageProducer; -import javax.jms.ObjectMessage; -import javax.jms.Queue; -import javax.jms.QueueBrowser; -import javax.jms.QueueReceiver; -import javax.jms.QueueSender; -import javax.jms.QueueSession; -import javax.jms.Session; -import javax.jms.StreamMessage; -import javax.jms.TemporaryQueue; -import javax.jms.TemporaryTopic; -import javax.jms.TextMessage; -import javax.jms.Topic; -import javax.jms.TopicPublisher; -import javax.jms.TopicSession; -import javax.jms.TopicSubscriber; - -/** Wraps a real {@link Session} but simulates a JMS 1.0 provider. */ -public class Jms10Session implements QueueSession, TopicSession { - private final Session delegate; - - public Jms10Session(Session delegate) { - this.delegate = delegate; - } - - // --- JMS 1.1-only unified Session methods — not present in JMS 1.0 --- - - @Override - public MessageProducer createProducer(Destination destination) throws JMSException { - return delegate.createProducer(destination); - } - - @Override - public MessageConsumer createConsumer(Destination destination) throws JMSException { - return delegate.createConsumer(destination); - } - - @Override - public MessageConsumer createConsumer(Destination destination, String messageSelector) - throws JMSException { - return delegate.createConsumer(destination, messageSelector); - } - - @Override - public MessageConsumer createConsumer( - Destination destination, String messageSelector, boolean noLocal) throws JMSException { - return delegate.createConsumer(destination, messageSelector, noLocal); - } - - // --- JMS 1.0 QueueSession methods --- - - @Override - public Queue createQueue(String queueName) throws JMSException { - return delegate.createQueue(queueName); - } - - @Override - public QueueReceiver createReceiver(Queue queue) throws JMSException { - return new Jms10QueueReceiver(delegate.createConsumer(queue), queue); - } - - @Override - public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException { - return new Jms10QueueReceiver(delegate.createConsumer(queue, messageSelector), queue); - } - - @Override - public QueueSender createSender(Queue queue) throws JMSException { - return new Jms10QueueSender(delegate.createProducer(queue), queue); - } - - @Override - public QueueBrowser createBrowser(Queue queue) throws JMSException { - return delegate.createBrowser(queue); - } - - @Override - public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException { - return delegate.createBrowser(queue, messageSelector); - } - - @Override - public TemporaryQueue createTemporaryQueue() throws JMSException { - return delegate.createTemporaryQueue(); - } - - // --- JMS 1.0 TopicSession methods --- - - @Override - public Topic createTopic(String topicName) throws JMSException { - return delegate.createTopic(topicName); - } - - @Override - public TopicSubscriber createSubscriber(Topic topic) throws JMSException { - return new Jms10TopicSubscriber(delegate.createConsumer(topic), topic, false); - } - - @Override - public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) - throws JMSException { - return new Jms10TopicSubscriber( - delegate.createConsumer(topic, messageSelector, noLocal), topic, noLocal); - } - - @Override - public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException { - return new Jms10TopicSubscriber(delegate.createDurableSubscriber(topic, name), topic, false); - } - - @Override - public TopicSubscriber createDurableSubscriber( - Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException { - return new Jms10TopicSubscriber( - delegate.createDurableSubscriber(topic, name, messageSelector, noLocal), topic, noLocal); - } - - @Override - public TopicPublisher createPublisher(Topic topic) throws JMSException { - return new Jms10TopicPublisher(delegate.createProducer(topic), topic); - } - - @Override - public TemporaryTopic createTemporaryTopic() throws JMSException { - return delegate.createTemporaryTopic(); - } - - @Override - public void unsubscribe(String name) throws JMSException { - delegate.unsubscribe(name); - } - - // --- Common Session methods --- - - @Override - public BytesMessage createBytesMessage() throws JMSException { - return delegate.createBytesMessage(); - } - - @Override - public MapMessage createMapMessage() throws JMSException { - return delegate.createMapMessage(); - } - - @Override - public Message createMessage() throws JMSException { - return delegate.createMessage(); - } - - @Override - public ObjectMessage createObjectMessage() throws JMSException { - return delegate.createObjectMessage(); - } - - @Override - public ObjectMessage createObjectMessage(Serializable object) throws JMSException { - return delegate.createObjectMessage(object); - } - - @Override - public StreamMessage createStreamMessage() throws JMSException { - return delegate.createStreamMessage(); - } - - @Override - public TextMessage createTextMessage() throws JMSException { - return delegate.createTextMessage(); - } - - @Override - public TextMessage createTextMessage(String text) throws JMSException { - return delegate.createTextMessage(text); - } - - @Override - public boolean getTransacted() throws JMSException { - return delegate.getTransacted(); - } - - @Override - public int getAcknowledgeMode() { - throw new AbstractMethodError("JMS 1.0 provider does not implement getAcknowledgeMode()"); - } - - @Override - public void commit() throws JMSException { - delegate.commit(); - } - - @Override - public void rollback() throws JMSException { - delegate.rollback(); - } - - @Override - public void close() throws JMSException { - delegate.close(); - } - - @Override - public void recover() throws JMSException { - delegate.recover(); - } - - @Override - public MessageListener getMessageListener() throws JMSException { - return delegate.getMessageListener(); - } - - @Override - public void setMessageListener(MessageListener listener) throws JMSException { - delegate.setMessageListener(listener); - } - - @Override - public void run() { - delegate.run(); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java deleted file mode 100644 index 6f3c1e38663..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java +++ /dev/null @@ -1,137 +0,0 @@ -package jms10mock; - -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageProducer; -import javax.jms.Topic; -import javax.jms.TopicPublisher; - -/** Wraps a real {@link MessageProducer} but simulates a JMS 1.0 provider. */ -public class Jms10TopicPublisher implements TopicPublisher { - private final MessageProducer delegate; - private final Topic topic; - - public Jms10TopicPublisher(MessageProducer delegate, Topic topic) { - this.delegate = delegate; - this.topic = topic; - } - - // --- JMS 1.1-only methods — not present in JMS 1.0 --- - - @Override - public Destination getDestination() { - throw new AbstractMethodError("JMS 1.0 provider does not implement getDestination()"); - } - - @Override - public void send(Destination destination, Message message) throws JMSException { - delegate.send(destination, message); - } - - @Override - public void send( - Destination destination, Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(destination, message, deliveryMode, priority, timeToLive); - } - - // --- JMS 1.0 TopicPublisher methods --- - - @Override - public Topic getTopic() { - return topic; - } - - @Override - public void publish(Message message) throws JMSException { - delegate.send(message); - } - - @Override - public void publish(Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(message, deliveryMode, priority, timeToLive); - } - - @Override - public void publish(Topic topic, Message message) throws JMSException { - delegate.send(topic, message); - } - - @Override - public void publish(Topic topic, Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(topic, message, deliveryMode, priority, timeToLive); - } - - // --- MessageProducer send methods (also available via publish in 1.0) --- - - @Override - public void send(Message message) throws JMSException { - delegate.send(message); - } - - @Override - public void send(Message message, int deliveryMode, int priority, long timeToLive) - throws JMSException { - delegate.send(message, deliveryMode, priority, timeToLive); - } - - // --- MessageProducer config methods --- - - @Override - public void close() throws JMSException { - delegate.close(); - } - - @Override - public void setDisableMessageID(boolean value) throws JMSException { - delegate.setDisableMessageID(value); - } - - @Override - public boolean getDisableMessageID() throws JMSException { - return delegate.getDisableMessageID(); - } - - @Override - public void setDisableMessageTimestamp(boolean value) throws JMSException { - delegate.setDisableMessageTimestamp(value); - } - - @Override - public boolean getDisableMessageTimestamp() throws JMSException { - return delegate.getDisableMessageTimestamp(); - } - - @Override - public void setDeliveryMode(int deliveryMode) throws JMSException { - delegate.setDeliveryMode(deliveryMode); - } - - @Override - public int getDeliveryMode() throws JMSException { - return delegate.getDeliveryMode(); - } - - @Override - public void setPriority(int defaultPriority) throws JMSException { - delegate.setPriority(defaultPriority); - } - - @Override - public int getPriority() throws JMSException { - return delegate.getPriority(); - } - - @Override - public void setTimeToLive(long timeToLive) throws JMSException { - delegate.setTimeToLive(timeToLive); - } - - @Override - public long getTimeToLive() throws JMSException { - return delegate.getTimeToLive(); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java deleted file mode 100644 index 97ca5ea2343..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java +++ /dev/null @@ -1,66 +0,0 @@ -package jms10mock; - -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageListener; -import javax.jms.Topic; -import javax.jms.TopicSubscriber; - -/** Wraps a real {@link MessageConsumer} but simulates a JMS 1.0 provider. */ -public class Jms10TopicSubscriber implements TopicSubscriber { - private final MessageConsumer delegate; - private final Topic topic; - private final boolean noLocal; - - public Jms10TopicSubscriber(MessageConsumer delegate, Topic topic, boolean noLocal) { - this.delegate = delegate; - this.topic = topic; - this.noLocal = noLocal; - } - - @Override - public Topic getTopic() { - return topic; - } - - @Override - public boolean getNoLocal() { - return noLocal; - } - - @Override - public String getMessageSelector() throws JMSException { - return delegate.getMessageSelector(); - } - - @Override - public MessageListener getMessageListener() throws JMSException { - return delegate.getMessageListener(); - } - - @Override - public void setMessageListener(MessageListener listener) throws JMSException { - delegate.setMessageListener(listener); - } - - @Override - public Message receive() throws JMSException { - return delegate.receive(); - } - - @Override - public Message receive(long timeout) throws JMSException { - return delegate.receive(timeout); - } - - @Override - public Message receiveNoWait() throws JMSException { - return delegate.receiveNoWait(); - } - - @Override - public void close() throws JMSException { - delegate.close(); - } -} From b120fe1ed584399d0d8631b7ba5d32b6852a4543 Mon Sep 17 00:00:00 2001 From: Jordan Wong Date: Mon, 1 Jun 2026 10:34:26 -0400 Subject: [PATCH 4/4] feat(jms): toolkit-generated JMS 1.1 + Jakarta JMS 3.0 instrumentation [DO NOT MERGE] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Blind test v2 — generated by APM Instrumentation Toolkit new_integration workflow from master 04a3a80382 (includes amarziali's #11413 JMS 1.0 work). Generated: javax-jms-1.1/ + jakarta-jms-3.0/ Reviewer: approved (todos_fixed=1, todos_remaining=0) Real cost: ~$63.51 Test iterations: 3 Co-Authored-By: Claude Sonnet 4.6 --- .../jms/jakarta-jms-3.0/build.gradle | 44 + .../instrumentation/jms/JakartaJmsModule.java | 24 + .../trace/instrumentation/jms/JMS3Test.java | 842 ++++++++++++ .../jms/javax-jms-1.1/build.gradle | 54 + .../jms/DatadogMessageListener.java | 107 ++ .../instrumentation/jms/JMSDecorator.java | 352 +++++ .../trace/instrumentation/jms/JMSLogger.java | 13 + .../JMSMessageConsumerInstrumentation.java | 247 ++++ .../JMSMessageProducerInstrumentation.java | 229 ++++ .../instrumentation/jms/JavaxJmsModule.java | 63 + .../MDBMessageConsumerInstrumentation.java | 133 ++ .../jms/MessageExtractAdapter.java | 81 ++ .../jms/MessageInjectAdapter.java | 45 + .../jms/MessageInstrumentation.java | 53 + .../jms/SessionInstrumentation.java | 220 +++ .../trace/instrumentation/jms/JMS1Test.java | 1209 +++++++++++++++++ 16 files changed, 3716 insertions(+) create mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle create mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java create mode 100644 dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java create mode 100644 dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle new file mode 100644 index 00000000000..87c94dfa151 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle @@ -0,0 +1,44 @@ +muzzle { + pass { + name = "jakarta.jms" + group = "jakarta.jms" + module = "jakarta.jms-api" + versions = "[3.0.0,4)" + javaVersion = "17" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +testJvmConstraints { + minJavaVersion = JavaVersion.VERSION_17 +} + +repositories { + maven { + // only place that has org.jboss.naming:jnpserver:5.0.3.GA publicly accessible + name = 'jboss-releases' + url = 'https://repository.jboss.org/nexus/content/repositories/releases/' + } +} + +tasks.named("compileTestGroovy", GroovyCompile) { + configureCompiler(it, 17) +} + +tasks.named("compileTestJava", JavaCompile) { + configureCompiler(it, 17) +} + +dependencies { + implementation project(':dd-java-agent:instrumentation:jms:javax-jms-1.1') + + testImplementation 'jakarta.jms:jakarta.jms-api:3.0.0' + testImplementation 'jakarta.ejb:jakarta.ejb-api:4.0.0' + testImplementation group: 'org.springframework', name: 'spring-jms', version: '6.0.11' + testImplementation group: 'org.springframework', name: 'spring-context', version: '6.0.11' + testImplementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.1.1' + testImplementation group: 'org.hornetq', name: 'hornetq-jakarta-client', version: '2.4.9.Final' + testImplementation group: 'org.hornetq', name: 'hornetq-jms-server', version: '2.4.9.Final' +} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java new file mode 100644 index 00000000000..7b8ead719b5 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/main/java/datadog/trace/instrumentation/jms/JakartaJmsModule.java @@ -0,0 +1,24 @@ +package datadog.trace.instrumentation.jms; + +import static java.util.Collections.singletonMap; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.InstrumenterModule; +import java.util.Map; + +@AutoService(InstrumenterModule.class) +public class JakartaJmsModule extends JavaxJmsModule { + public JakartaJmsModule() { + super("jakarta", "jakarta-jms", "jms"); + } + + @Override + public String muzzleDirective() { + return "jakarta.jms"; + } + + @Override + public Map adviceShading() { + return singletonMap("javax", "jakarta"); + } +} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java new file mode 100644 index 00000000000..38548fcf47d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java @@ -0,0 +1,842 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import jakarta.jms.Connection; +import jakarta.jms.ConnectionFactory; +import jakarta.jms.DeliveryMode; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.MessageConsumer; +import jakarta.jms.MessageListener; +import jakarta.jms.MessageProducer; +import jakarta.jms.Queue; +import jakarta.jms.Session; +import jakarta.jms.TemporaryQueue; +import jakarta.jms.TemporaryTopic; +import jakarta.jms.TextMessage; +import jakarta.jms.Topic; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.hornetq.api.core.TransportConfiguration; +import org.hornetq.api.jms.HornetQJMSClient; +import org.hornetq.api.jms.JMSFactoryType; +import org.hornetq.core.config.Configuration; +import org.hornetq.core.config.CoreQueueConfiguration; +import org.hornetq.core.config.impl.ConfigurationImpl; +import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory; +import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory; +import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory; +import org.hornetq.core.server.HornetQServer; +import org.hornetq.core.server.HornetQServers; +import org.hornetq.jms.server.impl.JMSServerManagerImpl; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Integration tests for Jakarta JMS 3.0 instrumentation covering producer, consumer, and message + * listener spans. Validates span structure, operation names, resource names, span types, span + * kinds, tags, error propagation, and distributed trace context propagation between producer and + * consumer. + * + *

Uses an embedded HornetQ broker with the Jakarta JMS client for realistic end-to-end testing. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class JMS3Test extends AbstractInstrumentationTest { + + static { + // Ensure tracing is enabled before the agent is installed by the parent @BeforeAll. + // This protects against DD_TRACE_ENABLED=false leaking from the environment. + System.setProperty("dd.trace.enabled", "true"); + } + + private HornetQServer server; + private JMSServerManagerImpl jmsServerManager; + private Connection connection; + private Session session; + private int queueCounter = 0; + + @BeforeAll + void setupBroker() throws Exception { + Configuration config = new ConfigurationImpl(); + config.setBindingsDirectory(System.getProperty("java.io.tmpdir") + "/hornetq-bindings-jms3"); + config.setJournalDirectory(System.getProperty("java.io.tmpdir") + "/hornetq-journal-jms3"); + config.setCreateBindingsDir(true); + config.setCreateJournalDir(true); + config.setSecurityEnabled(false); + config.setPersistenceEnabled(false); + config.setQueueConfigurations( + Collections.singletonList( + new CoreQueueConfiguration("someQueue", "someQueue", null, true))); + + HashSet acceptors = new HashSet<>(); + acceptors.add(new TransportConfiguration(NettyAcceptorFactory.class.getName())); + acceptors.add(new TransportConfiguration(InVMAcceptorFactory.class.getName())); + config.setAcceptorConfigurations(acceptors); + + server = HornetQServers.newHornetQServer(config); + + // Wrap in JMSServerManager for dynamic JMS queue/topic creation. + // Do NOT start the core server separately — the JMS manager's start() handles that + // and registers its ActivateCallback so dynamic queue/topic creation works. + jmsServerManager = new JMSServerManagerImpl(server); + jmsServerManager.setContext(null); + jmsServerManager.start(); + + // Pre-create JMS topic for topic tests + jmsServerManager.createTopic(false, "someTopic", "/topic/someTopic"); + + ConnectionFactory connectionFactory = + HornetQJMSClient.createConnectionFactoryWithoutHA( + JMSFactoryType.CF, new TransportConfiguration(InVMConnectorFactory.class.getName())); + connection = connectionFactory.createConnection(); + connection.start(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + session.run(); + } + + @AfterAll + void tearDownBroker() throws Exception { + if (session != null) { + session.close(); + } + if (connection != null) { + connection.close(); + } + if (jmsServerManager != null) { + jmsServerManager.stop(); + } + if (server != null) { + server.stop(); + } + } + + /** Returns a unique queue for each test to avoid cross-test data leaks. */ + private Queue createUniqueQueue() throws Exception { + String queueName = "test-queue-" + (queueCounter++); + jmsServerManager.createQueue(false, queueName, null, true); + return session.createQueue(queueName); + } + + // ===================== Producer Tests ===================== + + @Test + void sendToQueueCreatesProducerSpanWithCorrectAttributes() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello queue"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + // Operation name + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + // Span type -- JMS producer and consumer spans use type "queue" + assertEquals("queue", producerSpan.getSpanType()); + // Resource name should contain "Produced for Queue " + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") + && resourceName.contains("Queue") + && resourceName.contains(queue.getQueueName()), + "Resource name should be 'Produced for Queue ', got: " + resourceName); + // Tags + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Producer span should have messaging.operation=send"); + // Measured flag + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + // No error + assertFalse(producerSpan.isError(), "Producer span should not be errored"); + // Root span (no parent) + assertEquals(0L, producerSpan.getParentId(), "Producer span should be a root span"); + } + + @Test + void sendToTopicCreatesProducerSpanWithTopicResourceName() throws Exception { + Topic topic = session.createTopic("someTopic"); + MessageProducer producer = session.createProducer(topic); + TextMessage message = session.createTextMessage("hello topic"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for topic"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") && resourceName.contains("Topic"), + "Resource name should contain 'Produced for' and 'Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Topic producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Topic producer span should have messaging.operation=send"); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithExplicitDestinationCreatesProducerSpan() throws Exception { + Queue queue = createUniqueQueue(); + // Create producer without default destination + MessageProducer producer = session.createProducer(null); + TextMessage message = session.createTextMessage("hello explicit dest"); + + producer.send(queue, message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for explicit destination"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithDeliveryModeAndPriorityCreatesProducerSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello with params"); + + // Send with explicit deliveryMode, priority, and timeToLive + producer.send(message, DeliveryMode.NON_PERSISTENT, 7, 60000); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span with delivery params"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendToTemporaryQueueCreatesProducerSpanWithTemporaryResourceName() throws Exception { + TemporaryQueue tempQueue = session.createTemporaryQueue(); + MessageProducer producer = session.createProducer(tempQueue); + TextMessage message = session.createTextMessage("hello temp queue"); + + producer.send(message); + + producer.close(); + tempQueue.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary queue"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Queue"), + "Resource name for temp queue should contain 'Temporary Queue', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + @Test + void sendToTemporaryTopicCreatesProducerSpanWithTemporaryResourceName() throws Exception { + TemporaryTopic tempTopic = session.createTemporaryTopic(); + MessageProducer producer = session.createProducer(tempTopic); + TextMessage message = session.createTextMessage("hello temp topic"); + + producer.send(message); + + producer.close(); + tempTopic.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary topic"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Topic"), + "Resource name for temp topic should contain 'Temporary Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + // ===================== Consumer Tests ===================== + + @Test + void receiveCreatesConsumerSpanLinkedToProducer() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("receive test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received a message"); + assertEquals("receive test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Consumer span attributes + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Consumer receive() span should have messaging.operation=receive"); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + assertFalse(consumerSpan.isError(), "Consumer span should not be errored"); + + // Context propagation: consumer should be child of producer + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer span should be child of the producer span (distributed context propagation)"); + assertEquals( + producerSpan.getTraceId(), + consumerSpan.getTraceId(), + "Consumer and producer spans should share the same trace ID"); + } + + @Test + void receiveWithTimeoutCreatesConsumerSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("timeout receive"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received message with timeout"); + assertEquals("timeout receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receive(timeout)"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void receiveNoWaitCreatesConsumerSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("noWait receive"); + producer.send(sentMessage); + + // Allow time for the message to arrive at the broker + Thread.sleep(500); + + TextMessage receivedMessage = (TextMessage) consumer.receiveNoWait(); + + assertNotNull(receivedMessage, "Should have received message with receiveNoWait"); + assertEquals("noWait receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receiveNoWait"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void multipleMessagesCreateMultipleProducerAndConsumerSpans() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + producer.send(session.createTextMessage("msg1")); + producer.send(session.createTextMessage("msg2")); + producer.send(session.createTextMessage("msg3")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + TextMessage recv3 = (TextMessage) consumer.receive(5000); + + assertNotNull(recv1, "Should receive message 1"); + assertNotNull(recv2, "Should receive message 2"); + assertNotNull(recv3, "Should receive message 3"); + assertEquals("msg1", recv1.getText()); + assertEquals("msg2", recv2.getText()); + assertEquals("msg3", recv3.getText()); + + producer.close(); + + // Trigger last pending consumer span to finish (receiveNoWait causes previous span flush) + consumer.receiveNoWait(); + consumer.close(); + + // 3 producer traces + up to 3 consumer traces + writer.waitForTraces(6); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(3, producerSpans.size(), "Expected 3 producer spans"); + assertEquals(3, consumerSpans.size(), "Expected 3 consumer spans"); + + // Each producer span should have consistent attributes + for (DDSpan pSpan : producerSpans) { + assertEquals("producer", String.valueOf(pSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(pSpan.getTag("component"))); + assertEquals("queue", pSpan.getSpanType()); + } + + // Each consumer span should be linked to a producer span + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertEquals("queue", cSpan.getSpanType()); + // Consumer should be child of a producer (distributed context) + assertTrue( + cSpan.getParentId() != 0, + "Consumer span should have a parent (linked via context propagation)"); + } + } + + @Test + void consumerReceiveFromTopicCreatesConsumerSpan() throws Exception { + Topic topic = session.createTopic("someTopic"); + MessageProducer producer = session.createProducer(topic); + MessageConsumer consumer = session.createConsumer(topic); + + TextMessage sentMessage = session.createTextMessage("topic consume test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should have received topic message"); + assertEquals("topic consume test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for topic"); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Topic"), + "Consumer resource should mention 'Consumed from Topic', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Topic consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Topic consumer span should have messaging.operation=receive"); + } + + // ===================== MessageListener Tests ===================== + + @Test + void messageListenerOnMessageCreatesConsumerSpanLinkedToProducer() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + TestMessageListener listener = new TestMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Listener should receive the message"); + assertNotNull(listener.receivedMessage, "Listener should have captured the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + + // Find the consumer/deliver span created by the MessageListener instrumentation + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull( + listenerSpan, "Expected a consumer or deliver span for MessageListener.onMessage"); + assertEquals("queue", listenerSpan.getSpanType()); + // Resource name should follow the 'Consumed from Queue ' pattern + String listenerResource = listenerSpan.getResourceName().toString(); + assertTrue( + listenerResource.contains("Consumed from") && listenerResource.contains("Queue"), + "Listener span resource should contain 'Consumed from Queue', got: " + listenerResource); + assertEquals("jms", String.valueOf(listenerSpan.getTag("component"))); + String listenerKind = String.valueOf(listenerSpan.getTag("span.kind")); + assertTrue( + "consumer".equals(listenerKind) || "broker".equals(listenerKind), + "Listener span kind should be 'consumer' or 'broker', got: " + listenerKind); + // Messaging semantic convention tags for listener (process operation) + assertEquals( + "jms", + listenerSpan.getTag("messaging.system"), + "Listener span should have messaging.system=jms"); + assertEquals( + "process", + listenerSpan.getTag("messaging.operation"), + "MessageListener.onMessage span should have messaging.operation=process"); + assertFalse(listenerSpan.isError(), "Listener span should not be errored"); + // Linked to producer via context propagation + assertEquals( + producerSpan.getTraceId(), + listenerSpan.getTraceId(), + "Listener span should share trace ID with producer"); + } + + @Test + void messageListenerErrorIsRecordedOnSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + FailingMessageListener listener = new FailingMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("error listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Failing listener should process the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + // Find the listener span -- it should be errored + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull(listenerSpan, "Expected a consumer/deliver span for failing listener"); + assertTrue(listenerSpan.isError(), "Listener span should be marked as errored"); + assertNotNull( + listenerSpan.getTag("error.message"), "Errored span should have error.message tag"); + assertNotNull(listenerSpan.getTag("error.type"), "Errored span should have error.type tag"); + assertNotNull(listenerSpan.getTag("error.stack"), "Errored span should have error.stack tag"); + } + + // ===================== Context Propagation Tests ===================== + + @Test + void producerSpanIsChildOfActiveSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("child span test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertEquals( + parentSpan.getSpanId(), + producerSpan.getParentId(), + "Producer span should be a child of the active parent span"); + assertEquals( + parentSpan.context().getTraceId(), + producerSpan.getTraceId(), + "Producer and parent should share trace ID"); + } + + @Test + void distributedContextPropagatesThroughQueueSendAndReceive() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + // Send under a parent span to verify full trace linkage + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("distributed context test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive message with distributed context"); + assertEquals("distributed context test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify the full trace chain: parent -> producer -> consumer + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (distributed context)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + // ===================== Error Handling Tests ===================== + + @Test + void sendOnClosedProducerRecordsError() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + producer.close(); + + JMSException caughtException = null; + try { + TextMessage message = session.createTextMessage("error test"); + producer.send(message); + } catch (JMSException e) { + caughtException = e; + } + + assertNotNull(caughtException, "Sending on closed producer should throw JMSException"); + + // Allow time for any spans to complete + Thread.sleep(500); + + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + // If the instrumentation creates a span around the failed send, it should be errored + if (producerSpan != null) { + assertTrue(producerSpan.isError(), "Producer span on failed send should be errored"); + assertNotNull( + producerSpan.getTag("error.type"), "Errored producer span should have error.type tag"); + assertNotNull( + producerSpan.getTag("error.message"), + "Errored producer span should have error.message tag"); + } + } + + // ===================== Transacted Session Tests ===================== + + @Test + void receiveInTransactedSessionCreatesConsumerSpanOnCommit() throws Exception { + Queue queue = createUniqueQueue(); + Session transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = transactedSession.createConsumer(queue); + + producer.send(session.createTextMessage("transacted msg1")); + producer.send(session.createTextMessage("transacted msg2")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + transactedSession.commit(); + + assertNotNull(recv1, "Should receive first transacted message"); + assertNotNull(recv2, "Should receive second transacted message"); + assertEquals("transacted msg1", recv1.getText()); + assertEquals("transacted msg2", recv2.getText()); + + producer.close(); + consumer.close(); + transactedSession.close(); + + writer.waitForTraces(4); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(2, producerSpans.size(), "Expected 2 producer spans"); + assertEquals(2, consumerSpans.size(), "Expected 2 consumer spans after commit"); + + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertFalse(cSpan.isError()); + } + } + + // ===================== Helper Methods ===================== + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpanByOperationAndKind( + List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + private List findAllSpansByOperation(List spans, String operationName) { + List result = new ArrayList<>(); + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName)) { + result.add(span); + } + } + return result; + } + + /** Finds the consumer or deliver span created by MessageListener instrumentation. */ + private DDSpan findConsumerOrDeliverSpan(List spans) { + for (DDSpan span : spans) { + String opName = span.getOperationName().toString(); + if ("jms.consume".equals(opName) || "jms.deliver".equals(opName)) { + return span; + } + } + return null; + } + + /** Simple MessageListener that captures the received message. */ + static class TestMessageListener implements MessageListener { + volatile Message receivedMessage; + private final CountDownLatch latch; + + TestMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + receivedMessage = message; + latch.countDown(); + } + } + + /** MessageListener that throws a RuntimeException to test error recording. */ + static class FailingMessageListener implements MessageListener { + private final CountDownLatch latch; + + FailingMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + latch.countDown(); + throw new RuntimeException("Intentional error in message listener"); + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle new file mode 100644 index 00000000000..74b9d050a9a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle @@ -0,0 +1,54 @@ +muzzle { + pass { + name = "javax.jms" + group = "javax.jms" + module = "jms-api" + versions = "[,]" + } + pass { + name = "javax.jms" + group = "javax.jms" + module = "javax.jms-api" + versions = "[,]" + } +} + +apply from: "$rootDir/gradle/java.gradle" +apply plugin: 'java-test-fixtures' + +repositories { + maven { + // only place that has org.jboss.naming:jnpserver:5.0.3.GA publicly accessible + name = 'jboss-releases' + url = 'https://repository.jboss.org/nexus/content/repositories/releases/' + } +} + +addTestSuite('latestDepTest') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') + +tasks.named("latestDepTest", Test) { + finalizedBy 'latestDepForkedTest' +} + +dependencies { + compileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + + testFixturesCompileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + + testImplementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation') + testImplementation group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5' + testImplementation group: 'org.apache.activemq', name: 'activemq-pool', version: '5.14.5' + testImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.14.5' + + // required for Java 11+ . Latest version that runs on Java 7 + testImplementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.2' + testImplementation group: 'org.springframework', name: 'spring-jms', version: '4.3.21.RELEASE' // 4.x required for Java 7 + testImplementation group: 'javax.ejb', name: 'javax.ejb-api', version: '3.2' + + latestDepTestImplementation group: 'org.hornetq', name: 'hornetq-jms-client', version: '2.4.7.Final' + latestDepTestImplementation group: 'org.hornetq', name: 'hornetq-jms-server', version: '2.4.7.Final' + + // For this module Groovy 3 needs `org.junit.rules.ExternalResource` from JUnit4. + testImplementation(libs.junit4) +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java new file mode 100644 index 00000000000..d4fb55f79f6 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java @@ -0,0 +1,107 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; +import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Message; +import javax.jms.MessageListener; + +public class DatadogMessageListener implements MessageListener { + + private final ContextStore messageAckStore; + private final MessageConsumerState consumerState; + private final MessageListener messageListener; + + public DatadogMessageListener( + ContextStore messageAckStore, + MessageConsumerState consumerState, + MessageListener messageListener) { + this.messageAckStore = messageAckStore; + this.consumerState = consumerState; + this.messageListener = messageListener; + } + + @Override + public void onMessage(Message message) { + AgentSpan span; + AgentSpanContext propagatedContext = null; + if (!consumerState.isPropagationDisabled()) { + propagatedContext = extractContextAndGetSpanContext(message, GETTER); + } + long startMillis = GETTER.extractTimeInQueueStart(message); + if (startMillis == 0 || !TIME_IN_QUEUE_ENABLED) { + span = startSpan("jms", JMS_CONSUME, propagatedContext); + } else { + long batchId = GETTER.extractMessageBatchId(message); + AgentSpan timeInQueue = consumerState.getTimeInQueueSpan(batchId); + if (null == timeInQueue) { + timeInQueue = + startSpan("jms", JMS_DELIVER, propagatedContext, MILLISECONDS.toMicros(startMillis)); + BROKER_DECORATE.afterStart(timeInQueue); + BROKER_DECORATE.onTimeInQueue( + timeInQueue, + consumerState.getBrokerResourceName(), + consumerState.getBrokerServiceName()); + consumerState.setTimeInQueueSpan(batchId, timeInQueue); + } + span = startSpan("jms", JMS_CONSUME, timeInQueue.context()); + } + CONSUMER_DECORATE.afterStart(span); + String listenerDestinationName = + consumerState.getConsumerBaseResourceName() != null + ? consumerState.getConsumerBaseResourceName().toString() + : null; + CONSUMER_DECORATE.onConsume( + span, message, consumerState.getConsumerResourceName(), listenerDestinationName, "process"); + + if (Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = + create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + + SessionState sessionState = consumerState.getSessionState(); + if (sessionState.isClientAcknowledge()) { + // consumed spans will be finished by a call to Message.acknowledge + sessionState.finishOnAcknowledge(span); + messageAckStore.put(message, sessionState); + } else if (sessionState.isTransactedSession()) { + // span will be finished by Session.commit/rollback/close + sessionState.finishOnCommit(span); + } + try (AgentScope scope = activateSpan(span)) { + messageListener.onMessage(message); + } catch (RuntimeException | Error thrown) { + CONSUMER_DECORATE.onError(span, thrown); + throw thrown; + } finally { + if (sessionState.isAutoAcknowledge()) { + span.finish(); + consumerState.finishTimeInQueueSpan(false); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java new file mode 100644 index 00000000000..3ca91620a2a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java @@ -0,0 +1,352 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_DESTINATION_NAME; +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_OPERATION; +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_SYSTEM; +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.RECORD_QUEUE_TIME_MS; + +import datadog.trace.api.Config; +import datadog.trace.api.Functions.Join; +import datadog.trace.api.Functions.PrefixJoin; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.MessagingClientDecorator; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueSender; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; +import javax.jms.Topic; +import javax.jms.TopicPublisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class JMSDecorator extends MessagingClientDecorator { + private static final Logger log = LoggerFactory.getLogger(JMSDecorator.class); + + public static final CharSequence JMS = UTF8BytesString.create("jms"); + public static final CharSequence JMS_CONSUME = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().inboundOperation(JMS.toString())); + public static final CharSequence JMS_PRODUCE = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().outboundOperation(JMS.toString())); + public static final CharSequence JMS_DELIVER = UTF8BytesString.create("jms.deliver"); + + public static final boolean JMS_LEGACY_TRACING = Config.get().isJmsLegacyTracingEnabled(); + + public static final boolean TIME_IN_QUEUE_ENABLED = + Config.get().isTimeInQueueEnabled(!JMS_LEGACY_TRACING, "jms"); + public static final String JMS_PRODUCED_KEY = "x_datadog_jms_produced"; + public static final String JMS_BATCH_ID_KEY = "x_datadog_jms_batch_id"; + + private static final Join QUEUE_JOINER = PrefixJoin.of("Queue "); + private static final Join TOPIC_JOINER = PrefixJoin.of("Topic "); + + private final DDCache resourceNameCache = + DDCaches.newFixedSizeCache(32); + + private final String resourcePrefix; + + private final UTF8BytesString queueTempResourceName; + private final UTF8BytesString topicTempResourceName; + + private final Function queueResourceJoiner; + private final Function topicResourceJoiner; + + private final String spanKind; + private final CharSequence spanType; + private final Supplier serviceNameSupplier; + + public static final JMSDecorator PRODUCER_DECORATE = + new JMSDecorator( + "Produced for ", + Tags.SPAN_KIND_PRODUCER, + InternalSpanTypes.MESSAGE_PRODUCER, + SpanNaming.instance() + .namingSchema() + .messaging() + .outboundService("jms", JMS_LEGACY_TRACING)); + + public static final JMSDecorator CONSUMER_DECORATE = + new JMSDecorator( + "Consumed from ", + Tags.SPAN_KIND_CONSUMER, + InternalSpanTypes.MESSAGE_CONSUMER, + SpanNaming.instance() + .namingSchema() + .messaging() + .inboundService("jms", JMS_LEGACY_TRACING)); + + public static final JMSDecorator BROKER_DECORATE = + new JMSDecorator( + "", + Tags.SPAN_KIND_BROKER, + InternalSpanTypes.MESSAGE_BROKER, + SpanNaming.instance().namingSchema().messaging().timeInQueueService(JMS.toString())); + + public JMSDecorator( + String resourcePrefix, + String spanKind, + CharSequence spanType, + Supplier serviceNameSupplier) { + this.resourcePrefix = resourcePrefix; + + this.queueTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Queue"); + this.topicTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Topic"); + + this.queueResourceJoiner = QUEUE_JOINER.curry(resourcePrefix); + this.topicResourceJoiner = TOPIC_JOINER.curry(resourcePrefix); + + this.spanKind = spanKind; + this.spanType = spanType; + this.serviceNameSupplier = serviceNameSupplier; + } + + public static void logJMSException(JMSException ex) { + if (log.isDebugEnabled()) { + log.debug("JMS exception during instrumentation", ex); + } + } + + public static String messageTechnology(Message m) { + if (null == m) { + return "jms"; + } + + String messageClass = m.getClass().getName(); + + if (messageClass.startsWith("com.amazon.sqs")) { + return "sqs"; + } else if (messageClass.startsWith("com.ibm")) { + return "ibmmq"; + } else { + return "jms"; + } + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"jms"}; + } + + @Override + protected CharSequence spanType() { + return spanType; + } + + @Override + protected String service() { + return serviceNameSupplier.get(); + } + + @Override + protected CharSequence component() { + return JMS; + } + + @Override + protected String spanKind() { + return spanKind; + } + + public void onConsume(AgentSpan span, Message message, CharSequence resourceName) { + onConsume(span, message, resourceName, null, "receive"); + } + + public void onConsume( + AgentSpan span, Message message, CharSequence resourceName, String destinationName) { + onConsume(span, message, resourceName, destinationName, "receive"); + } + + public void onConsume( + AgentSpan span, + Message message, + CharSequence resourceName, + String destinationName, + String operation) { + span.setTag(MESSAGING_SYSTEM, "jms"); + span.setTag(MESSAGING_OPERATION, operation); + if (null != resourceName) { + span.setResourceName(resourceName); + } + if (null != destinationName && !destinationName.isEmpty()) { + span.setTag(MESSAGING_DESTINATION_NAME, destinationName); + } + + try { + final long produceTime = message.getJMSTimestamp(); + if (produceTime > 0) { + final long consumeTime = TimeUnit.NANOSECONDS.toMillis(span.getStartTime()); + span.setTag(RECORD_QUEUE_TIME_MS, Math.max(0L, consumeTime - produceTime)); + } + } catch (Exception e) { + log.debug("Unable to get jms timestamp", e); + } + } + + public void onProduce(AgentSpan span, CharSequence resourceName) { + onProduce(span, resourceName, null); + } + + public void onProduce(AgentSpan span, CharSequence resourceName, String destinationName) { + span.setTag(MESSAGING_SYSTEM, "jms"); + span.setTag(MESSAGING_OPERATION, "send"); + if (null != resourceName) { + span.setResourceName(resourceName); + } + if (null != destinationName && !destinationName.isEmpty()) { + span.setTag(MESSAGING_DESTINATION_NAME, destinationName); + } + } + + public static boolean canInject(Message message) { + // JMS->SQS already stores the trace context in 'X-Amzn-Trace-Id' / 'AWSTraceHeader', + // so skip storing same context again to avoid SQS limit of 10 attributes per message. + return !message.getClass().getName().startsWith("com.amazon.sqs.javamessaging"); + } + + public void onTimeInQueue(AgentSpan span, CharSequence resourceName, String serviceName) { + if (null != resourceName) { + span.setResourceName(resourceName); + } + if (null != serviceName) { + span.setServiceName(serviceName, component()); + } + } + + private static final String TIBCO_TMP_PREFIX = "$TMP$"; + + /** + * Sanitizes destination names to remove Kafka Connect schema-derived suffixes. When Kafka + * Connect's IBM MQ connectors are used with schema converters (Protobuf/JSON Schema), union or + * optional fields may get index suffixes like _messagebody_0 appended to the queue name. + */ + private static String sanitizeDestinationName(String name) { + if (name == null || name.isEmpty()) { + return name; + } + + int len = name.length(); + + // Check if name ends with digits (the schema index suffix) + if (!Character.isDigit(name.charAt(len - 1))) { + return name; + } + + // Find the underscore before the trailing digits + int underscoreBeforeDigits = name.lastIndexOf('_'); + if (underscoreBeforeDigits <= 0) { + return name; + } + + // Verify all characters after the underscore are digits + for (int i = underscoreBeforeDigits + 1; i < len; i++) { + if (!Character.isDigit(name.charAt(i))) { + return name; + } + } + + // Find the underscore before the suffix word + int underscoreBeforeSuffix = name.lastIndexOf('_', underscoreBeforeDigits - 1); + if (underscoreBeforeSuffix < 0) { + return name; + } + + // Check if the suffix word is one of our known Kafka Connect schema suffixes (case insensitive) + int suffixStart = underscoreBeforeSuffix + 1; + int suffixLen = underscoreBeforeDigits - suffixStart; + + if (isKnownKafkaConnectSuffix(name, suffixStart, suffixLen)) { + return name.substring(0, underscoreBeforeSuffix); + } + + return name; + } + + private static boolean isKnownKafkaConnectSuffix(String name, int start, int len) { + return (len == 11 && name.regionMatches(true, start, "messagebody", 0, 11)) + || (len == 4 && name.regionMatches(true, start, "text", 0, 4)) + || (len == 5 && name.regionMatches(true, start, "bytes", 0, 5)) + || (len == 5 && name.regionMatches(true, start, "value", 0, 5)) + || (len == 3 && name.regionMatches(true, start, "map", 0, 3)); + } + + public CharSequence toResourceName(String destinationName, boolean isQueue) { + if (null == destinationName) { + return isQueue ? queueTempResourceName : topicTempResourceName; + } + Function joiner = + isQueue ? queueResourceJoiner : topicResourceJoiner; + // some systems may have queues and topics with the same name - since we won't know which was + // cached first we check the character after the initial prefix to see if it's Q (for Queue) - + // if that's what we expect we can use the cached value, otherwise generate the correct name + CharSequence resourceName = resourceNameCache.computeIfAbsent(destinationName, joiner); + if ((resourceName.charAt(resourcePrefix.length()) == 'Q') == isQueue) { + return resourceName; + } + return joiner.apply(destinationName); + } + + public Destination getDestination(final MessageProducer messageProducer) throws JMSException { + try { + return messageProducer.getDestination(); // >= 1.1 + } catch (AbstractMethodError ignored) { + // <=1.1 getDestination is not available so we need to pay an additional instanceOf + if (messageProducer instanceof QueueSender) { + return ((QueueSender) messageProducer).getQueue(); + } + return ((TopicPublisher) messageProducer).getTopic(); + } + } + + public String getDestinationName(Destination destination) { + String name = null; + try { + if (destination instanceof Queue) { + // WebLogic mixes all JMS Destination interfaces in a single base type which means we can't + // rely on instanceof and have to instead check the result of getQueueName vs getTopicName + if (!(destination instanceof TemporaryQueue) || isWebLogicDestination(destination)) { + name = ((Queue) destination).getQueueName(); + } + } + // check Topic name if Queue name is null because this might be a WebLogic destination + if (null == name && destination instanceof Topic) { + if (!(destination instanceof TemporaryTopic) || isWebLogicDestination(destination)) { + name = ((Topic) destination).getTopicName(); + } + } + } catch (Exception e) { + log.debug("Unable to get jms destination name", e); + } + if (null != name && !name.startsWith(TIBCO_TMP_PREFIX)) { + // Sanitize Kafka Connect schema-derived suffixes from queue/topic names + return sanitizeDestinationName(name); + } + return null; + } + + public boolean isQueue(Destination destination) { + try { + // handle WebLogic by treating everything as a Queue unless it's a Topic with a name + return !(destination instanceof Topic) || null == ((Topic) destination).getTopicName(); + } catch (Exception e) { + return true; // assume it's a Queue if we can't check the details + } + } + + private static boolean isWebLogicDestination(Destination destination) { + return destination.getClass().getName().startsWith("weblogic.jms.common."); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java new file mode 100644 index 00000000000..6618045254d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java @@ -0,0 +1,13 @@ +package datadog.trace.instrumentation.jms; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JMSLogger { + private static final Logger log = LoggerFactory.getLogger(JMSLogger.class); + + public static void logIterationSpan(AgentSpan span) { + log.debug("Expecting the following `ITERATION` span to be finished {}", span); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java new file mode 100644 index 00000000000..2874c77b281 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java @@ -0,0 +1,247 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateNext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.closePrevious; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; +import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.spanFromContext; +import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; +import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.Config; +import datadog.trace.api.InstrumenterConfig; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public final class JMSMessageConsumerInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public JMSMessageConsumerInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.MessageConsumer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("receive").and(takesArguments(0).or(takesArguments(1))).and(isPublic()), + JMSMessageConsumerInstrumentation.class.getName() + "$ConsumerAdvice"); + transformer.applyAdvice( + named("receiveNoWait").and(takesArguments(0)).and(isPublic()), + JMSMessageConsumerInstrumentation.class.getName() + "$ConsumerAdvice"); + transformer.applyAdvice( + named("close").and(takesArguments(0)).and(isPublic()), + JMSMessageConsumerInstrumentation.class.getName() + "$Close"); + transformer.applyAdvice( + isMethod() + .and(named("setMessageListener")) + .and(takesArgument(0, hasInterface(named(namespace + ".jms.MessageListener")))), + JMSMessageConsumerInstrumentation.class.getName() + "$DecorateMessageListener"); + } + + public static class ConsumerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static MessageConsumerState beforeReceive(@Advice.This final MessageConsumer consumer) { + MessageConsumerState consumerState = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) + .get(consumer); + + // ignore consumers who aren't bound to a tracked session via consumerState + if (null == consumerState) { + return null; + } + + boolean finishSpan = consumerState.getSessionState().isAutoAcknowledge(); + if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { + closePrevious(finishSpan); + } else { + final AgentSpan previousSpan = spanFromContext(getRootContext().swap()); + if (previousSpan != null) { + CONSUMER_DECORATE.beforeFinish(previousSpan); + previousSpan.finishWithEndToEnd(); + } + } + if (finishSpan) { + consumerState.finishTimeInQueueSpan(false); + } + + // don't create spans for nested receive calls, even if different consumers are involved + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageConsumer.class); + if (callDepth > 0) { + return null; + } + + return consumerState; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterReceive( + @Advice.Enter final MessageConsumerState consumerState, + @Advice.This final MessageConsumer consumer, + @Advice.Return final Message message, + @Advice.Thrown final Throwable throwable) { + + if (consumerState == null) { + // either we're not tracking the consumer or this is a nested receive + return; + } + + // outermost receive call - make sure we reset call-depth before returning + CallDepthThreadLocalMap.reset(MessageConsumer.class); + + if (message == null) { + // don't create spans (traces) for each poll if the queue is empty + return; + } + + AgentSpan span; + AgentSpanContext propagatedContext = null; + if (!consumerState.isPropagationDisabled()) { + propagatedContext = extractContextAndGetSpanContext(message, GETTER); + } + long startMillis = GETTER.extractTimeInQueueStart(message); + if (startMillis == 0 || !TIME_IN_QUEUE_ENABLED) { + span = startSpan("jms", JMS_CONSUME, propagatedContext); + } else { + long batchId = GETTER.extractMessageBatchId(message); + AgentSpan timeInQueue = consumerState.getTimeInQueueSpan(batchId); + if (null == timeInQueue) { + timeInQueue = + startSpan("jms", JMS_DELIVER, propagatedContext, MILLISECONDS.toMicros(startMillis)); + BROKER_DECORATE.afterStart(timeInQueue); + BROKER_DECORATE.onTimeInQueue( + timeInQueue, + consumerState.getBrokerResourceName(), + consumerState.getBrokerServiceName()); + consumerState.setTimeInQueueSpan(batchId, timeInQueue); + } + span = startSpan("jms", JMS_CONSUME, timeInQueue.context()); + } + + CONSUMER_DECORATE.afterStart(span); + String consumerDestinationName = + consumerState.getConsumerBaseResourceName() != null + ? consumerState.getConsumerBaseResourceName().toString() + : null; + CONSUMER_DECORATE.onConsume( + span, message, consumerState.getConsumerResourceName(), consumerDestinationName); + + if (Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = + create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + + CONSUMER_DECORATE.onError(span, throwable); + + if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { + activateNext(span); // scope is left open until next message or it times out + } else { + final AgentSpan previousSpan = spanFromContext(span.swap()); + if (previousSpan != null) { + CONSUMER_DECORATE.beforeFinish(previousSpan); + previousSpan.finishWithEndToEnd(); + } + } + JMSLogger.logIterationSpan(span); + + SessionState sessionState = consumerState.getSessionState(); + if (sessionState.isClientAcknowledge()) { + // consumed spans will be finished by a call to Message.acknowledge + sessionState.finishOnAcknowledge(span); + InstrumentationContext.get(Message.class, SessionState.class).put(message, sessionState); + } else if (sessionState.isTransactedSession()) { + // span will be finished by Session.commit/rollback/close + sessionState.finishOnCommit(span); + } + // for AUTO_ACKNOWLEDGE, span is not finished until next call to receive, or close + } + } + + public static class Close { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void beforeClose(@Advice.This final MessageConsumer consumer) { + MessageConsumerState consumerState = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) + .get(consumer); + if (null != consumerState) { + boolean finishSpan = consumerState.getSessionState().isAutoAcknowledge(); + if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { + closePrevious(finishSpan); + } else { + final AgentSpan previousSpan = spanFromContext(getRootContext().swap()); + if (previousSpan != null) { + CONSUMER_DECORATE.beforeFinish(previousSpan); + previousSpan.finishWithEndToEnd(); + } + } + if (finishSpan) { + consumerState.finishTimeInQueueSpan(true); + } + } + } + } + + public static class DecorateMessageListener { + @Advice.OnMethodEnter + public static void setMessageListener( + @Advice.This MessageConsumer messageConsumer, + @Advice.Argument(value = 0, readOnly = false) MessageListener listener) { + if (null != listener && !(listener instanceof DatadogMessageListener)) { + MessageConsumerState consumerState = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) + .get(messageConsumer); + if (null != consumerState) { + listener = + new DatadogMessageListener( + InstrumentationContext.get(Message.class, SessionState.class), + consumerState, + listener); + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java new file mode 100644 index 00000000000..76305896d9c --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java @@ -0,0 +1,229 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCE; +import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; +import static datadog.trace.instrumentation.jms.MessageInjectAdapter.SETTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import javax.jms.Destination; +import javax.jms.Message; +import javax.jms.MessageProducer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public final class JMSMessageProducerInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public JMSMessageProducerInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.MessageProducer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvices( + named("send").and(takesArgument(0, named(namespace + ".jms.Message"))).and(isPublic()), + JMSMessageProducerInstrumentation.class.getName() + "$ProducerAdvice", + JMSMessageProducerInstrumentation.class.getName() + "$ProducerContextPropagationAdvice"); + transformer.applyAdvices( + named("send") + .and(takesArgument(0, hasInterface(named(namespace + ".jms.Destination")))) + .and(takesArgument(1, named(namespace + ".jms.Message"))) + .and(isPublic()), + JMSMessageProducerInstrumentation.class.getName() + "$ProducerWithDestinationAdvice", + JMSMessageProducerInstrumentation.class.getName() + + "$ProducerWithDestinationContextPropagationAdvice"); + } + + public static class ProducerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope beforeSend( + @Advice.Argument(0) final Message message, @Advice.This final MessageProducer producer) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class); + if (callDepth > 0) { + return null; + } + + MessageProducerState producerState = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) + .get(producer); + + CharSequence resourceName; + String destinationName; + try { + // fall-back when producer wasn't created via standard Session.createProducer API + if (null != producerState) { + resourceName = producerState.getResourceName(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); + destinationName = PRODUCER_DECORATE.getDestinationName(destination); + } else { + Destination destination = PRODUCER_DECORATE.getDestination(producer); + destinationName = PRODUCER_DECORATE.getDestinationName(destination); + boolean isQueue = PRODUCER_DECORATE.isQueue(destination); + resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); + } + } catch (Exception ignored) { + resourceName = "Unknown Destination"; + destinationName = ""; + } + + final AgentSpan span = startSpan("jms", JMS_PRODUCE); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, resourceName, destinationName); + + if (null != destinationName + && !destinationName.isEmpty() + && Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = create(tech, OUTBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + + if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED && null != producerState) { + SETTER.injectTimeInQueue(message, producerState); + } + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterSend( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(MessageProducer.class); + } + } + + @AppliesOn(CONTEXT_TRACKING) + public static class ProducerContextPropagationAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) final Message message, @Advice.This final MessageProducer producer) { + AgentSpan span = activeSpan(); + if (span == null) return; + if (JMSDecorator.canInject(message) && Config.get().isJmsPropagationEnabled()) { + MessageProducerState producerState = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) + .get(producer); + if (null == producerState || !producerState.isPropagationDisabled()) { + defaultPropagator().inject(span, message, SETTER); + } + } + } + } + + public static class ProducerWithDestinationAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope beforeSend( + @Advice.Argument(0) final Destination destination, + @Advice.Argument(1) final Message message, + @Advice.This final MessageProducer producer) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class); + if (callDepth > 0) { + return null; + } + + boolean isQueue = PRODUCER_DECORATE.isQueue(destination); + String destinationName = PRODUCER_DECORATE.getDestinationName(destination); + CharSequence resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); + + final AgentSpan span = startSpan("jms", JMS_PRODUCE); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, resourceName, destinationName); + + if (null != destinationName + && !destinationName.isEmpty() + && Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = create(tech, OUTBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + + if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED) { + MessageProducerState producerState = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) + .get(producer); + if (null != producerState) { + SETTER.injectTimeInQueue(message, producerState); + } + } + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterSend( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(MessageProducer.class); + } + } + + @AppliesOn(CONTEXT_TRACKING) + public static class ProducerWithDestinationContextPropagationAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) final Destination destination, + @Advice.Argument(1) final Message message) { + AgentSpan span = activeSpan(); + if (span == null) return; + if (JMSDecorator.canInject(message) && Config.get().isJmsPropagationEnabled()) { + String destinationName = PRODUCER_DECORATE.getDestinationName(destination); + if (!Config.get().isJmsPropagationDisabledForDestination(destinationName)) { + defaultPropagator().inject(span, message, SETTER); + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java new file mode 100644 index 00000000000..9c0ef70a12b --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java @@ -0,0 +1,63 @@ +package datadog.trace.instrumentation.jms; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@AutoService(InstrumenterModule.class) +public class JavaxJmsModule extends InstrumenterModule.Tracing { + private final String namespace; + + public JavaxJmsModule() { + this("javax", "jms", "jms-1", "jms-2"); + } + + public JavaxJmsModule(String namespace, String instrumentationName, String... additionalNames) { + super(instrumentationName, additionalNames); + this.namespace = namespace; + } + + @Override + public String muzzleDirective() { + return "javax.jms"; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JMSDecorator", + packageName + ".MessageExtractAdapter", + packageName + ".MessageExtractAdapter$1", + packageName + ".MessageInjectAdapter", + packageName + ".DatadogMessageListener", + packageName + ".JMSLogger" + }; + } + + @Override + public Map contextStore() { + Map contextStore = new HashMap<>(4); + contextStore.put(namespace + ".jms.MessageConsumer", MessageConsumerState.class.getName()); + contextStore.put(namespace + ".jms.MessageProducer", MessageProducerState.class.getName()); + contextStore.put(namespace + ".jms.Message", SessionState.class.getName()); + contextStore.put(namespace + ".jms.Session", SessionState.class.getName()); + return contextStore; + } + + @Override + public List typeInstrumentations() { + return Arrays.asList( + new JMSMessageConsumerInstrumentation(namespace), + new JMSMessageProducerInstrumentation(namespace), + new MDBMessageConsumerInstrumentation(namespace), + new MessageInstrumentation(namespace), + new SessionInstrumentation(namespace)); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java new file mode 100644 index 00000000000..3636d63d9e8 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java @@ -0,0 +1,133 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresAnnotation; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; +import static datadog.trace.instrumentation.jms.JMSDecorator.logJMSException; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; +import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.context.ContextScope; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public final class MDBMessageConsumerInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public MDBMessageConsumerInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.MessageListener"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())) + .and( + hasSuperType(declaresAnnotation(named(namespace + ".ejb.MessageDriven"))) + .or(implementsInterface(named(namespace + ".ejb.MessageDrivenBean")))); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvices( + isMethod() + .and(isPublic()) + .and(named("onMessage")) + .and(takesArguments(1)) + .and(takesArgument(0, (named(namespace + ".jms.Message")))), + getClass().getName() + "$ContextPropagationAdvice", + getClass().getName() + "$MDBAdvice"); + } + + @AppliesOn(CONTEXT_TRACKING) + public static class ContextPropagationAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) final Message message, @Advice.Local("ctxScope") ContextScope scope) { + scope = defaultPropagator().extract(getRootContext(), message, GETTER).attach(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.Local("ctxScope") ContextScope scope) { + if (scope != null) scope.close(); + } + } + + public static class MDBAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope methodEnter(@Advice.Argument(0) final Message message) { + if (CallDepthThreadLocalMap.incrementCallDepth(MessageListener.class) > 0) { + return null; + } + AgentSpan span = startSpan("jms", JMS_CONSUME); + CONSUMER_DECORATE.afterStart(span); + CharSequence consumerResourceName; + String destinationName = null; + try { + Destination destination = message.getJMSDestination(); + boolean isQueue = CONSUMER_DECORATE.isQueue(destination); + destinationName = CONSUMER_DECORATE.getDestinationName(destination); + consumerResourceName = CONSUMER_DECORATE.toResourceName(destinationName, isQueue); + } catch (JMSException e) { + logJMSException(e); + consumerResourceName = "unknown JMS destination"; + } + CONSUMER_DECORATE.onConsume(span, message, consumerResourceName, destinationName, "process"); + + if (Config.get().isDataStreamsEnabled() + && null != destinationName + && !destinationName.isEmpty()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = create(tech, INBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.Enter AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (null != scope) { + CallDepthThreadLocalMap.reset(MessageListener.class); + CONSUMER_DECORATE.onError(scope, throwable); + scope.close(); + scope.span().finish(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java new file mode 100644 index 00000000000..6d233e3c276 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java @@ -0,0 +1,81 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_BATCH_ID_KEY; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCED_KEY; + +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.util.Enumeration; +import java.util.Locale; +import java.util.function.Function; +import javax.jms.JMSException; +import javax.jms.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class MessageExtractAdapter implements AgentPropagation.ContextVisitor { + private static final Logger log = LoggerFactory.getLogger(MessageExtractAdapter.class); + private static final Function KEY_MAPPER = + new Function() { + @SuppressForbidden + @Override + public String apply(String key) { + return key.replace("__dash__", "-").replace('$', '-').toLowerCase(Locale.ROOT); + } + }; + + private final DDCache cache = DDCaches.newFixedSizeCache(32); + + public static final MessageExtractAdapter GETTER = new MessageExtractAdapter(); + + @Override + public void forEachKey(Message carrier, AgentPropagation.KeyClassifier classifier) { + try { + final Enumeration enumeration = carrier.getPropertyNames(); + if (null != enumeration) { + while (enumeration.hasMoreElements()) { + String key = ((String) enumeration.nextElement()); + String lowerCaseKey = cache.computeIfAbsent(key, KEY_MAPPER); + Object value = null; + try { + value = carrier.getObjectProperty(key); + } catch (Throwable t) { + // log and ignore if we cannot access this property but don't break the instrumentation + if (log.isDebugEnabled()) { + log.debug("Error accessing message property {}", key, t); + } + } + if (value instanceof String && !classifier.accept(lowerCaseKey, (String) value)) { + return; + } + } + } + } catch (JMSException e) { + throw new RuntimeException(e); + } + } + + public long extractTimeInQueueStart(final Message carrier) { + try { + if (carrier.propertyExists(JMS_PRODUCED_KEY)) { + return carrier.getLongProperty(JMS_PRODUCED_KEY); + } + } catch (Exception e) { + log.debug("Unable to get jms produced time", e); + } + return 0; + } + + public long extractMessageBatchId(final Message carrier) { + try { + if (carrier.propertyExists(JMS_BATCH_ID_KEY)) { + return carrier.getLongProperty(JMS_BATCH_ID_KEY); + } + } catch (Exception e) { + log.debug("Unable to get jms batch id", e); + } + return 0; + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java new file mode 100644 index 00000000000..7d2e558fc79 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java @@ -0,0 +1,45 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_BATCH_ID_KEY; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCED_KEY; + +import datadog.context.propagation.CarrierSetter; +import datadog.trace.bootstrap.instrumentation.jms.MessageBatchState; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import de.thetaphi.forbiddenapis.SuppressForbidden; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.jms.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageInjectAdapter implements CarrierSetter { + private static final Logger log = LoggerFactory.getLogger(MessageInjectAdapter.class); + + public static final MessageInjectAdapter SETTER = new MessageInjectAdapter(); + + @ParametersAreNonnullByDefault + @SuppressForbidden + @Override + public void set(final Message carrier, final String key, final String value) { + final String propName = key.replace("-", "__dash__"); + try { + carrier.setStringProperty(propName, value); + } catch (Exception e) { + log.debug("Failure setting jms property: {}", propName, e); + } + } + + public void injectTimeInQueue(final Message carrier, final MessageProducerState producerState) { + try { + if (producerState.getSessionState().isTransactedSession()) { + MessageBatchState batchState = producerState.currentBatchState(); + carrier.setLongProperty(JMS_BATCH_ID_KEY, batchState.getBatchId()); + carrier.setLongProperty(JMS_PRODUCED_KEY, batchState.getStartMillis()); + } else { + carrier.setLongProperty(JMS_PRODUCED_KEY, System.currentTimeMillis()); + } + } catch (Exception e) { + log.debug("Failure setting jms batch details", e); + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java new file mode 100644 index 00000000000..8bfda025d32 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java @@ -0,0 +1,53 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameStartsWith; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Message; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class MessageInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public MessageInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.Message"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + nameStartsWith("acknowledge").and(isMethod()).and(isPublic()).and(takesNoArguments()), + getClass().getName() + "$Acknowledge"); + } + + public static final class Acknowledge { + @Advice.OnMethodExit + public static void acknowledge(@Advice.This Message message) { + SessionState sessionState = + InstrumentationContext.get(Message.class, SessionState.class).get(message); + if (null != sessionState && sessionState.isClientAcknowledge()) { + sessionState.onAcknowledgeOrRecover(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java new file mode 100644 index 00000000000..8c3ffa48231 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java @@ -0,0 +1,220 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; +import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_LEGACY_TRACING; +import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.Config; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Destination; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SessionInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public SessionInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.Session"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("createProducer")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Destination"))), + getClass().getName() + "$CreateProducer"); + transformer.applyAdvice( + isMethod() + .and(named("createSender")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Queue"))), + getClass().getName() + "$CreateProducer"); + transformer.applyAdvice( + isMethod() + .and(named("createPublisher")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Topic"))), + getClass().getName() + "$CreateProducer"); + + transformer.applyAdvice( + isMethod() + .and(named("createConsumer")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Destination"))), + getClass().getName() + "$CreateConsumer"); + transformer.applyAdvice( + isMethod() + .and(named("createReceiver")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Queue"))), + getClass().getName() + "$CreateConsumer"); + transformer.applyAdvice( + isMethod() + .and(namedOneOf("createSubscriber", "createDurableSubscriber")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Topic"))), + getClass().getName() + "$CreateConsumer"); + + transformer.applyAdvice( + namedOneOf("recover").and(takesNoArguments()), getClass().getName() + "$Recover"); + transformer.applyAdvice( + namedOneOf("commit", "rollback").and(takesNoArguments()), getClass().getName() + "$Commit"); + transformer.applyAdvice( + named("close").and(takesNoArguments()), getClass().getName() + "$Close"); + } + + public static final class CreateProducer { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void bindProducerState( + @Advice.This Session session, + @Advice.Argument(0) Destination destination, + @Advice.Return MessageProducer producer) { + + ContextStore producerStateStore = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class); + + // avoid doing the same thing more than once when there is delegation to overloads + if (producerStateStore.get(producer) == null) { + ContextStore sessionStateStore = + InstrumentationContext.get(Session.class, SessionState.class); + + SessionState sessionState = sessionStateStore.get(session); + if (null == sessionState) { + int ackMode; + try { + ackMode = session.getAcknowledgeMode(); + } catch (Throwable ignored) { + ackMode = Session.AUTO_ACKNOWLEDGE; + } + sessionState = + sessionStateStore.putIfAbsent( + session, new SessionState(ackMode, TIME_IN_QUEUE_ENABLED)); + } + + boolean isQueue = PRODUCER_DECORATE.isQueue(destination); + String destinationName = PRODUCER_DECORATE.getDestinationName(destination); + CharSequence resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); + + boolean propagationDisabled = + Config.get().isJmsPropagationDisabledForDestination(destinationName); + + producerStateStore.put( + producer, new MessageProducerState(sessionState, resourceName, propagationDisabled)); + } + } + } + + public static final class CreateConsumer { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void bindConsumerState( + @Advice.This Session session, + @Advice.Argument(0) Destination destination, + @Advice.Return MessageConsumer consumer) { + + ContextStore consumerStateStore = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class); + + // avoid doing the same thing more than once when there is delegation to overloads + if (consumerStateStore.get(consumer) == null) { + ContextStore sessionStateStore = + InstrumentationContext.get(Session.class, SessionState.class); + + SessionState sessionState = sessionStateStore.get(session); + if (null == sessionState) { + int ackMode; + try { + ackMode = session.getAcknowledgeMode(); + } catch (Throwable ignored) { + ackMode = Session.AUTO_ACKNOWLEDGE; + } + sessionState = + sessionStateStore.putIfAbsent( + session, new SessionState(ackMode, TIME_IN_QUEUE_ENABLED)); + } + + boolean isQueue = CONSUMER_DECORATE.isQueue(destination); + String destinationName = CONSUMER_DECORATE.getDestinationName(destination); + CharSequence brokerResourceName = + JMS_LEGACY_TRACING ? "jms" : BROKER_DECORATE.toResourceName(destinationName, isQueue); + CharSequence consumerResourceName = + CONSUMER_DECORATE.toResourceName(destinationName, isQueue); + + boolean propagationDisabled = + Config.get().isJmsPropagationDisabledForDestination(destinationName); + + consumerStateStore.put( + consumer, + new MessageConsumerState( + sessionState, + brokerResourceName, + destinationName, + consumerResourceName, + propagationDisabled)); + } + } + } + + public static final class Recover { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void recover(@Advice.This Session session) { + SessionState sessionState = + InstrumentationContext.get(Session.class, SessionState.class).get(session); + if (null != sessionState && sessionState.isClientAcknowledge()) { + sessionState.onAcknowledgeOrRecover(); + } + } + } + + public static final class Commit { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void commit(@Advice.This Session session) { + SessionState sessionState = + InstrumentationContext.get(Session.class, SessionState.class).get(session); + if (null != sessionState && sessionState.isTransactedSession()) { + sessionState.onCommitOrRollback(); + } + } + } + + public static final class Close { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void close(@Advice.This Session session) { + SessionState sessionState = + InstrumentationContext.get(Session.class, SessionState.class).get(session); + if (null != sessionState) { + sessionState.onClose(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java new file mode 100644 index 00000000000..fcc0e89667a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java @@ -0,0 +1,1209 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.DeliveryMode; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; +import javax.jms.TextMessage; +import javax.jms.Topic; +import org.apache.activemq.junit.EmbeddedActiveMQBroker; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Integration tests for JMS 1.1 instrumentation covering producer, consumer, and message listener + * spans. Validates span structure, operation names, resource names, span types, span kinds, tags, + * error propagation, and distributed trace context propagation between producer and consumer. + * + *

Uses an embedded ActiveMQ broker for realistic end-to-end testing. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class JMS1Test extends AbstractInstrumentationTest { + + static { + // Ensure tracing is enabled before the agent is installed by the parent @BeforeAll. + // This protects against DD_TRACE_ENABLED=false leaking from the environment. + System.setProperty("dd.trace.enabled", "true"); + } + + private EmbeddedActiveMQBroker broker; + private Connection connection; + private Session session; + private int queueCounter = 0; + + @BeforeAll + void setupBroker() throws JMSException { + broker = new EmbeddedActiveMQBroker(); + broker.start(); + ConnectionFactory connectionFactory = broker.createConnectionFactory(); + connection = connectionFactory.createConnection(); + connection.start(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + } + + @AfterAll + void tearDownBroker() throws JMSException { + if (session != null) { + session.close(); + } + if (connection != null) { + connection.close(); + } + if (broker != null) { + broker.stop(); + } + } + + /** Returns a unique queue for each test to avoid cross-test data leaks. */ + private Queue createUniqueQueue() throws JMSException { + return session.createQueue("test-queue-" + (queueCounter++)); + } + + // ===================== Producer Tests ===================== + + @Test + void sendToQueueCreatesProducerSpanWithCorrectAttributes() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello queue"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + // Operation name + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + // Span type — JMS producer and consumer spans use type "queue" + assertEquals("queue", producerSpan.getSpanType()); + // Resource name should contain "Produced for Queue " + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") + && resourceName.contains("Queue") + && resourceName.contains(queue.getQueueName()), + "Resource name should be 'Produced for Queue ', got: " + resourceName); + // Tags + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Producer span should have messaging.operation=send"); + // Measured flag + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + // No error + assertFalse(producerSpan.isError(), "Producer span should not be errored"); + // Root span (no parent) + assertEquals(0L, producerSpan.getParentId(), "Producer span should be a root span"); + } + + @Test + void sendToTopicCreatesProducerSpanWithTopicResourceName() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-producer"); + MessageProducer producer = session.createProducer(topic); + TextMessage message = session.createTextMessage("hello topic"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for topic"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") && resourceName.contains("Topic"), + "Resource name should contain 'Produced for' and 'Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Topic producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Topic producer span should have messaging.operation=send"); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithExplicitDestinationCreatesProducerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + // Create producer without default destination + MessageProducer producer = session.createProducer(null); + TextMessage message = session.createTextMessage("hello explicit dest"); + + producer.send(queue, message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for explicit destination"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithDeliveryModeAndPriorityCreatesProducerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello with params"); + + // Send with explicit deliveryMode, priority, and timeToLive + producer.send(message, DeliveryMode.NON_PERSISTENT, 7, 60000); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span with delivery params"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendToTemporaryQueueCreatesProducerSpanWithTemporaryResourceName() + throws JMSException, InterruptedException, TimeoutException { + TemporaryQueue tempQueue = session.createTemporaryQueue(); + MessageProducer producer = session.createProducer(tempQueue); + TextMessage message = session.createTextMessage("hello temp queue"); + + producer.send(message); + + producer.close(); + tempQueue.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary queue"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Queue"), + "Resource name for temp queue should contain 'Temporary Queue', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + @Test + void sendToTemporaryTopicCreatesProducerSpanWithTemporaryResourceName() + throws JMSException, InterruptedException, TimeoutException { + TemporaryTopic tempTopic = session.createTemporaryTopic(); + MessageProducer producer = session.createProducer(tempTopic); + TextMessage message = session.createTextMessage("hello temp topic"); + + producer.send(message); + + producer.close(); + tempTopic.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary topic"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Topic"), + "Resource name for temp topic should contain 'Temporary Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + // ===================== Consumer Tests ===================== + + @Test + void receiveCreatesConsumerSpanLinkedToProducer() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("receive test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received a message"); + assertEquals("receive test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Consumer span attributes + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Consumer receive() span should have messaging.operation=receive"); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + assertFalse(consumerSpan.isError(), "Consumer span should not be errored"); + + // Context propagation: consumer should be child of producer + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer span should be child of the producer span (distributed context propagation)"); + assertEquals( + producerSpan.getTraceId(), + consumerSpan.getTraceId(), + "Consumer and producer spans should share the same trace ID"); + } + + @Test + void receiveWithTimeoutCreatesConsumerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("timeout receive"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received message with timeout"); + assertEquals("timeout receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receive(timeout)"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void receiveNoWaitCreatesConsumerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("noWait receive"); + producer.send(sentMessage); + + // Allow time for the message to arrive at the broker + Thread.sleep(500); + + TextMessage receivedMessage = (TextMessage) consumer.receiveNoWait(); + + assertNotNull(receivedMessage, "Should have received message with receiveNoWait"); + assertEquals("noWait receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receiveNoWait"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void multipleMessagesCreateMultipleProducerAndConsumerSpans() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + producer.send(session.createTextMessage("msg1")); + producer.send(session.createTextMessage("msg2")); + producer.send(session.createTextMessage("msg3")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + TextMessage recv3 = (TextMessage) consumer.receive(5000); + + assertNotNull(recv1, "Should receive message 1"); + assertNotNull(recv2, "Should receive message 2"); + assertNotNull(recv3, "Should receive message 3"); + assertEquals("msg1", recv1.getText()); + assertEquals("msg2", recv2.getText()); + assertEquals("msg3", recv3.getText()); + + producer.close(); + + // Trigger last pending consumer span to finish (receiveNoWait causes previous span flush) + consumer.receiveNoWait(); + consumer.close(); + + // 3 producer traces + up to 3 consumer traces + writer.waitForTraces(6); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(3, producerSpans.size(), "Expected 3 producer spans"); + assertEquals(3, consumerSpans.size(), "Expected 3 consumer spans"); + + // Each producer span should have consistent attributes + for (DDSpan pSpan : producerSpans) { + assertEquals("producer", String.valueOf(pSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(pSpan.getTag("component"))); + assertEquals("queue", pSpan.getSpanType()); + } + + // Each consumer span should be linked to a producer span + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertEquals("queue", cSpan.getSpanType()); + // Consumer should be child of a producer (distributed context) + assertTrue( + cSpan.getParentId() != 0, + "Consumer span should have a parent (linked via context propagation)"); + } + } + + @Test + void consumerReceiveFromTopicCreatesConsumerSpan() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-consumer"); + MessageProducer producer = session.createProducer(topic); + MessageConsumer consumer = session.createConsumer(topic); + + TextMessage sentMessage = session.createTextMessage("topic consume test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should have received topic message"); + assertEquals("topic consume test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for topic"); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Topic"), + "Consumer resource should mention 'Consumed from Topic', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Topic consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Topic consumer span should have messaging.operation=receive"); + } + + // ===================== MessageListener Tests ===================== + + @Test + void messageListenerOnMessageCreatesConsumerSpanLinkedToProducer() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + TestMessageListener listener = new TestMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Listener should receive the message"); + assertNotNull(listener.receivedMessage, "Listener should have captured the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + + // Find the consumer/deliver span created by the MessageListener instrumentation + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull( + listenerSpan, "Expected a consumer or deliver span for MessageListener.onMessage"); + assertEquals("queue", listenerSpan.getSpanType()); + // Resource name should follow the 'Consumed from Queue ' pattern + String listenerResource = listenerSpan.getResourceName().toString(); + assertTrue( + listenerResource.contains("Consumed from") && listenerResource.contains("Queue"), + "Listener span resource should contain 'Consumed from Queue', got: " + listenerResource); + assertEquals("jms", String.valueOf(listenerSpan.getTag("component"))); + String listenerKind = String.valueOf(listenerSpan.getTag("span.kind")); + assertTrue( + "consumer".equals(listenerKind) || "broker".equals(listenerKind), + "Listener span kind should be 'consumer' or 'broker', got: " + listenerKind); + // Messaging semantic convention tags for listener (process operation) + assertEquals( + "jms", + listenerSpan.getTag("messaging.system"), + "Listener span should have messaging.system=jms"); + assertEquals( + "process", + listenerSpan.getTag("messaging.operation"), + "MessageListener.onMessage span should have messaging.operation=process"); + assertFalse(listenerSpan.isError(), "Listener span should not be errored"); + // Linked to producer via context propagation + assertEquals( + producerSpan.getTraceId(), + listenerSpan.getTraceId(), + "Listener span should share trace ID with producer"); + } + + @Test + void messageListenerErrorIsRecordedOnSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + FailingMessageListener listener = new FailingMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("error listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Failing listener should process the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + // Find the listener span — it should be errored + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull(listenerSpan, "Expected a consumer/deliver span for failing listener"); + assertTrue(listenerSpan.isError(), "Listener span should be marked as errored"); + assertNotNull( + listenerSpan.getTag("error.message"), "Errored span should have error.message tag"); + assertNotNull(listenerSpan.getTag("error.type"), "Errored span should have error.type tag"); + assertNotNull(listenerSpan.getTag("error.stack"), "Errored span should have error.stack tag"); + } + + // ===================== Context Propagation Tests ===================== + + @Test + void producerSpanIsChildOfActiveSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("child span test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertEquals( + parentSpan.getSpanId(), + producerSpan.getParentId(), + "Producer span should be a child of the active parent span"); + assertEquals( + parentSpan.context().getTraceId(), + producerSpan.getTraceId(), + "Producer and parent should share trace ID"); + } + + @Test + void distributedContextPropagatesThroughQueueSendAndReceive() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + // Send under a parent span to verify full trace linkage + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("distributed context test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive message with distributed context"); + assertEquals("distributed context test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify the full trace chain: parent -> producer -> consumer + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (distributed context)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + @Test + void distributedContextPropagatesThroughTopicSendAndReceive() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-ctx-prop"); + MessageProducer producer = session.createProducer(topic); + MessageConsumer consumer = session.createConsumer(topic); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("topic context propagation"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive topic message with distributed context"); + assertEquals("topic context propagation", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify producer span structure + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + String producerResource = producerSpan.getResourceName().toString(); + assertTrue( + producerResource.contains("Produced for") && producerResource.contains("Topic"), + "Producer resource should contain 'Produced for Topic', got: " + producerResource); + + // Verify consumer span structure + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Topic"), + "Consumer resource should contain 'Consumed from Topic', got: " + consumerResource); + + // Verify the full trace chain: parent -> producer -> consumer + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (distributed context via topic)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + @Test + void distributedContextPropagatesThroughExplicitDestinationSend() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + // Create producer without default destination (explicit destination on send) + MessageProducer producer = session.createProducer(null); + MessageConsumer consumer = session.createConsumer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("explicit dest context propagation"); + producer.send(queue, message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive message sent with explicit destination"); + assertEquals("explicit dest context propagation", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify producer span structure for explicit destination send + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + + // Verify consumer span structure + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + + // Verify context propagated even with explicit destination send + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (explicit destination send)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + @Test + void distributedContextPropagatesThroughMessageListener() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + TestMessageListener listener = new TestMessageListener(latch); + consumer.setMessageListener(listener); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("listener context propagation"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Listener should receive the message"); + assertNotNull(listener.receivedMessage, "Listener should have captured the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + + // Verify producer span structure + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + + // Find consumer/deliver span created by MessageListener + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull(listenerSpan, "Expected a consumer or deliver span from MessageListener"); + + // Verify listener span structure + assertEquals("queue", listenerSpan.getSpanType()); + assertEquals("jms", String.valueOf(listenerSpan.getTag("component"))); + String listenerKind = String.valueOf(listenerSpan.getTag("span.kind")); + assertTrue( + "consumer".equals(listenerKind) || "broker".equals(listenerKind), + "Listener span kind should be 'consumer' or 'broker', got: " + listenerKind); + assertFalse(listenerSpan.isError(), "Listener span should not be errored"); + + // Producer should be child of parent + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + + // All spans in same trace — verifies context propagated through message properties + assertEquals( + parentSpan.context().getTraceId(), + producerSpan.getTraceId(), + "Producer should share trace ID with parent"); + assertEquals( + parentSpan.context().getTraceId(), + listenerSpan.getTraceId(), + "Listener span should share trace ID with parent (distributed context via listener)"); + } + + @Test + void distributedContextPropagatesThroughReceiveNoWait() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("receiveNoWait context propagation"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + // Allow time for message to arrive at broker + Thread.sleep(500); + + TextMessage receivedMessage = (TextMessage) consumer.receiveNoWait(); + assertNotNull(receivedMessage, "Should receive message with receiveNoWait"); + assertEquals("receiveNoWait context propagation", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify producer span structure + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + + // Verify consumer span structure + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + + // Verify context propagated through receiveNoWait + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (context propagation via receiveNoWait)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + // ===================== Error Handling Tests ===================== + + @Test + void sendOnClosedProducerRecordsError() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + producer.close(); + + JMSException caughtException = null; + try { + TextMessage message = session.createTextMessage("error test"); + producer.send(message); + } catch (JMSException e) { + caughtException = e; + } + + assertNotNull(caughtException, "Sending on closed producer should throw JMSException"); + + // Allow time for any spans to complete + Thread.sleep(500); + + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + // If the instrumentation creates a span around the failed send, it should be errored + if (producerSpan != null) { + assertTrue(producerSpan.isError(), "Producer span on failed send should be errored"); + assertNotNull( + producerSpan.getTag("error.type"), "Errored producer span should have error.type tag"); + assertNotNull( + producerSpan.getTag("error.message"), + "Errored producer span should have error.message tag"); + } + } + + // ===================== Peer Service / Messaging Destination Tests ===================== + + @Test + void producerSpanHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("peer service test"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + + // Verify messaging.destination.name tag is set for peer service computation + Object destinationTag = producerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, + "Producer span should have 'messaging.destination.name' tag for peer service"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains(queue.getQueueName()), + "messaging.destination.name should contain the queue name '" + + queue.getQueueName() + + "', got: " + + destinationName); + } + + @Test + void producerSpanToTopicHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-peer-svc"); + MessageProducer producer = session.createProducer(topic); + TextMessage message = session.createTextMessage("topic peer service test"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for topic"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + + // Verify messaging.destination.name tag is set for topic + Object destinationTag = producerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, "Producer span should have 'messaging.destination.name' tag for topic"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains("test-topic-peer-svc"), + "messaging.destination.name should contain the topic name, got: " + destinationName); + } + + @Test + void explicitDestinationSendHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(null); + TextMessage message = session.createTextMessage("explicit dest peer service"); + + producer.send(queue, message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for explicit destination"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + + // Verify messaging.destination.name tag is set for explicit destination send + Object destinationTag = producerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, + "Producer span should have 'messaging.destination.name' for explicit destination send"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains(queue.getQueueName()), + "messaging.destination.name should contain the queue name, got: " + destinationName); + } + + @Test + void consumerSpanHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("consumer peer service test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should have received a message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + + // Verify messaging.destination.name tag is set on consumer span + Object destinationTag = consumerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, + "Consumer span should have 'messaging.destination.name' tag for peer service"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains(queue.getQueueName()), + "messaging.destination.name should contain the queue name, got: " + destinationName); + } + + // ===================== Transacted Session Tests ===================== + + @Test + void receiveInTransactedSessionCreatesConsumerSpanOnCommit() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + Session transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = transactedSession.createConsumer(queue); + + producer.send(session.createTextMessage("transacted msg1")); + producer.send(session.createTextMessage("transacted msg2")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + transactedSession.commit(); + + assertNotNull(recv1, "Should receive first transacted message"); + assertNotNull(recv2, "Should receive second transacted message"); + assertEquals("transacted msg1", recv1.getText()); + assertEquals("transacted msg2", recv2.getText()); + + producer.close(); + consumer.close(); + transactedSession.close(); + + writer.waitForTraces(4); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(2, producerSpans.size(), "Expected 2 producer spans"); + assertEquals(2, consumerSpans.size(), "Expected 2 consumer spans after commit"); + + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertFalse(cSpan.isError()); + } + } + + // ===================== Helper Methods ===================== + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpanByOperationAndKind( + List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + private List findAllSpansByOperation(List spans, String operationName) { + List result = new ArrayList<>(); + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName)) { + result.add(span); + } + } + return result; + } + + /** Finds the consumer or deliver span created by MessageListener instrumentation. */ + private DDSpan findConsumerOrDeliverSpan(List spans) { + for (DDSpan span : spans) { + String opName = span.getOperationName().toString(); + if ("jms.consume".equals(opName) || "jms.deliver".equals(opName)) { + return span; + } + } + return null; + } + + /** Simple MessageListener that captures the received message. */ + static class TestMessageListener implements MessageListener { + volatile Message receivedMessage; + private final CountDownLatch latch; + + TestMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + receivedMessage = message; + latch.countDown(); + } + } + + /** MessageListener that throws a RuntimeException to test error recording. */ + static class FailingMessageListener implements MessageListener { + private final CountDownLatch latch; + + FailingMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + latch.countDown(); + throw new RuntimeException("Intentional error in message listener"); + } + } +}