Skip to content

Commit 0cdf553

Browse files
jordan-wongclaude
andcommitted
feat(jms): regenerate JMS 1.1 instrumentation via AI toolkit (Run 9)
Generated by apm-instrumentation-toolkit new_integration workflow. Blind test — original instrumentation deleted before generation. LLM reviewer approved after 3 iterations. Module placed under javax-jms-1.1-gen/ to appear as fully new files in the PR diff (avoids confusion with existing javax-jms-1.1 module). This is a draft for review only — not intended for merge as-is. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 38ba446 commit 0cdf553

40 files changed

+6539
-483
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
muzzle {
2+
pass {
3+
group = "javax.jms"
4+
module = 'javax.jms-api'
5+
versions = "[1.1-rev-1,)"
6+
assertInverse = true
7+
}
8+
}
9+
10+
apply from: "$rootDir/gradle/java.gradle"
11+
12+
addTestSuiteForDir('latestDepTest', 'test')
13+
addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test')
14+
15+
dependencies {
16+
compileOnly group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
17+
18+
testImplementation group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
19+
testImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.16.7'
20+
21+
latestDepTestImplementation group: 'javax.jms', name: 'javax.jms-api', version: '+'
22+
latestDepTestImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.+'
23+
}

dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/gradle.lockfile

Lines changed: 163 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import com.google.common.io.Files
2+
import datadog.trace.agent.test.InstrumentationSpecification
3+
import datadog.trace.agent.test.asserts.ListWriterAssert
4+
import datadog.trace.api.DDSpanTypes
5+
import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags
6+
import datadog.trace.bootstrap.instrumentation.api.Tags
7+
import datadog.trace.core.DDSpan
8+
import org.hornetq.api.core.TransportConfiguration
9+
import org.hornetq.api.core.client.HornetQClient
10+
import org.hornetq.api.jms.HornetQJMSClient
11+
import org.hornetq.api.jms.JMSFactoryType
12+
import org.hornetq.core.config.Configuration
13+
import org.hornetq.core.config.CoreQueueConfiguration
14+
import org.hornetq.core.config.impl.ConfigurationImpl
15+
import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory
16+
import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory
17+
import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory
18+
import org.hornetq.core.server.HornetQServer
19+
import org.hornetq.core.server.HornetQServers
20+
import org.hornetq.jms.client.HornetQTextMessage
21+
import spock.lang.Shared
22+
23+
import javax.jms.Message
24+
import javax.jms.MessageListener
25+
import javax.jms.Session
26+
import javax.jms.TextMessage
27+
import java.util.concurrent.CountDownLatch
28+
import java.util.concurrent.atomic.AtomicReference
29+
30+
class JMS2Test extends InstrumentationSpecification {
31+
@Shared
32+
HornetQServer server
33+
@Shared
34+
String messageText = "a message"
35+
@Shared
36+
Session session
37+
38+
HornetQTextMessage message = session.createTextMessage(messageText)
39+
40+
def setupSpec() {
41+
def tempDir = Files.createTempDir()
42+
tempDir.deleteOnExit()
43+
44+
Configuration config = new ConfigurationImpl()
45+
config.bindingsDirectory = tempDir.path
46+
config.journalDirectory = tempDir.path
47+
config.createBindingsDir = false
48+
config.createJournalDir = false
49+
config.securityEnabled = false
50+
config.persistenceEnabled = false
51+
config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)])
52+
config.setAcceptorConfigurations([
53+
new TransportConfiguration(NettyAcceptorFactory.name),
54+
new TransportConfiguration(InVMAcceptorFactory.name)
55+
].toSet())
56+
57+
server = HornetQServers.newHornetQServer(config)
58+
server.start()
59+
60+
def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name))
61+
def sf = serverLocator.createSessionFactory()
62+
def clientSession = sf.createSession(false, false, false)
63+
clientSession.createQueue("jms.queue.someQueue", "jms.queue.someQueue", true)
64+
clientSession.createQueue("jms.topic.someTopic", "jms.topic.someTopic", true)
65+
clientSession.close()
66+
sf.close()
67+
serverLocator.close()
68+
69+
def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF,
70+
new TransportConfiguration(InVMConnectorFactory.name))
71+
72+
def connection = connectionFactory.createConnection()
73+
connection.start()
74+
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)
75+
session.run()
76+
}
77+
78+
def cleanupSpec() {
79+
server.stop()
80+
}
81+
82+
def "sending a message to #jmsResourceName generates spans"() {
83+
setup:
84+
def producer = session.createProducer(destination)
85+
def consumer = session.createConsumer(destination)
86+
87+
producer.send(message)
88+
89+
TextMessage receivedMessage = consumer.receive()
90+
// required to finish auto-acknowledged spans
91+
consumer.receiveNoWait()
92+
93+
expect:
94+
receivedMessage.text == messageText
95+
assertTraces(2) {
96+
producerTrace(it, jmsResourceName)
97+
consumerTrace(it, jmsResourceName, trace(0)[0])
98+
}
99+
100+
cleanup:
101+
producer.close()
102+
consumer.close()
103+
104+
where:
105+
destination | jmsResourceName
106+
session.createQueue("someQueue") | "Queue someQueue"
107+
session.createTopic("someTopic") | "Topic someTopic"
108+
session.createTemporaryQueue() | "Temporary Queue"
109+
session.createTemporaryTopic() | "Temporary Topic"
110+
}
111+
112+
def "sending to a MessageListener on #jmsResourceName generates a span"() {
113+
setup:
114+
def lock = new CountDownLatch(1)
115+
def messageRef = new AtomicReference<TextMessage>()
116+
def producer = session.createProducer(destination)
117+
def consumer = session.createConsumer(destination)
118+
consumer.setMessageListener new MessageListener() {
119+
@Override
120+
void onMessage(Message message) {
121+
lock.await() // ensure the producer trace is reported first.
122+
messageRef.set(message)
123+
}
124+
}
125+
126+
producer.send(message)
127+
lock.countDown()
128+
129+
expect:
130+
assertTraces(2) {
131+
producerTrace(it, jmsResourceName)
132+
consumerTrace(it, jmsResourceName, trace(0)[0])
133+
}
134+
// This check needs to go after all traces have been accounted for
135+
messageRef.get().text == messageText
136+
137+
cleanup:
138+
producer.close()
139+
consumer.close()
140+
141+
where:
142+
destination | jmsResourceName
143+
session.createQueue("someQueue") | "Queue someQueue"
144+
session.createTopic("someTopic") | "Topic someTopic"
145+
session.createTemporaryQueue() | "Temporary Queue"
146+
session.createTemporaryTopic() | "Temporary Topic"
147+
}
148+
149+
def "failing to receive message with receiveNoWait on #jmsResourceName works"() {
150+
setup:
151+
def consumer = session.createConsumer(destination)
152+
153+
// Receive with timeout
154+
TextMessage receivedMessage = consumer.receiveNoWait()
155+
// required to finish auto-acknowledged spans
156+
consumer.receiveNoWait()
157+
158+
expect:
159+
receivedMessage == null
160+
assertTraces(0) {}
161+
162+
cleanup:
163+
consumer.close()
164+
165+
where:
166+
destination | jmsResourceName
167+
session.createQueue("someQueue") | "Queue someQueue"
168+
session.createTopic("someTopic") | "Topic someTopic"
169+
}
170+
171+
def "failing to receive message with wait(timeout) on #jmsResourceName works"() {
172+
setup:
173+
def consumer = session.createConsumer(destination)
174+
175+
// Receive with timeout
176+
TextMessage receivedMessage = consumer.receive(1)
177+
// required to finish auto-acknowledged spans
178+
consumer.receiveNoWait()
179+
180+
expect:
181+
receivedMessage == null
182+
assertTraces(0) {}
183+
184+
cleanup:
185+
consumer.close()
186+
187+
where:
188+
destination | jmsResourceName
189+
session.createQueue("someQueue") | "Queue someQueue"
190+
session.createTopic("someTopic") | "Topic someTopic"
191+
}
192+
193+
def "sending a message with disabled timestamp generates spans without specific tag"() {
194+
setup:
195+
def producer = session.createProducer(session.createQueue("someQueue"))
196+
def consumer = session.createConsumer(session.createQueue("someQueue"))
197+
198+
producer.setDisableMessageTimestamp(true)
199+
boolean isTimeStampDisabled = producer.getDisableMessageTimestamp()
200+
producer.send(message)
201+
202+
consumer.receive()
203+
// required to finish auto-acknowledged spans
204+
consumer.receiveNoWait()
205+
206+
expect:
207+
assertTraces(2) {
208+
producerTrace(it, "Queue someQueue")
209+
consumerTrace(it, "Queue someQueue", trace(0)[0], isTimeStampDisabled)
210+
}
211+
212+
cleanup:
213+
producer.close()
214+
consumer.close()
215+
}
216+
217+
static producerTrace(ListWriterAssert writer, String jmsResourceName) {
218+
writer.trace(1) {
219+
span {
220+
parent()
221+
serviceName "jms"
222+
operationName "jms.produce"
223+
resourceName "Produced for $jmsResourceName"
224+
spanType DDSpanTypes.MESSAGE_PRODUCER
225+
errored false
226+
227+
tags {
228+
"$Tags.COMPONENT" "jms"
229+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER
230+
defaultTagsNoPeerService()
231+
}
232+
}
233+
}
234+
}
235+
236+
static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) {
237+
writer.trace(1) {
238+
span {
239+
childOf parentSpan
240+
serviceName "jms"
241+
operationName "jms.consume"
242+
resourceName "Consumed from $jmsResourceName"
243+
spanType DDSpanTypes.MESSAGE_CONSUMER
244+
errored false
245+
246+
tags {
247+
"${Tags.COMPONENT}" "jms"
248+
"${Tags.SPAN_KIND}" "consumer"
249+
if (!isTimestampDisabled) {
250+
"$InstrumentationTags.RECORD_QUEUE_TIME_MS" {it >= 0 }
251+
}
252+
defaultTagsNoPeerService(true)
253+
}
254+
}
255+
}
256+
}
257+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2020, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
19+
import datadog.trace.agent.test.InstrumentationSpecification
20+
import listener.Config
21+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
22+
import org.springframework.jms.core.JmsTemplate
23+
24+
import javax.jms.ConnectionFactory
25+
26+
import static JMS2Test.consumerTrace
27+
import static JMS2Test.producerTrace
28+
29+
class SpringListenerJMS2Test extends InstrumentationSpecification {
30+
31+
def "receiving message in spring listener generates spans"() {
32+
setup:
33+
def context = new AnnotationConfigApplicationContext(Config)
34+
def factory = context.getBean(ConnectionFactory)
35+
def template = new JmsTemplate(factory)
36+
template.convertAndSend("SpringListenerJMS2", "a message")
37+
38+
expect:
39+
assertTraces(2) {
40+
producerTrace(it, "Queue SpringListenerJMS2")
41+
consumerTrace(it, "Queue SpringListenerJMS2", trace(0)[0])
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)