Skip to content

Commit 6871226

Browse files
authored
feat(tck): Add Jakarta Messaging 3.1 TCK with some fixes (#1712)
* feat(tck): adding Jakarta Messaging 3.1 TCK (#1713) * feat(client): update JMS version metadata to Jakarta Messaging 3.1 (#1718) ActiveMQ reports JMS version as 1.1 (JMS 1.1 era). Since the project now uses jakarta.jms (Jakarta Messaging 3.1), the metadata must match. - getJMSVersion(): "1.1" → "3.1" - getJMSMajorVersion(): 1 → 3 * fix(client): use correct exception types per Jakarta Messaging spec (#1722) (#1724) (#1719) Several methods threw wrong exception types, causing TCK failures: - MessageProducer.send() with null destination on an unbound producer threw UnsupportedOperationException instead of InvalidDestinationException - JMSProducer.send() with null message threw NPE instead of MessageFormatRuntimeException - checkClientIDWasManuallySpecified() threw JMSException instead of jakarta.jms.IllegalStateException for durable subscribers without a client ID * fix(client): validate inputs before UnsupportedOperationException in shared consumer methods (#1719) The shared consumer methods (createSharedConsumer, createSharedDurableConsumer) threw UnsupportedOperationException immediately without validating inputs. Per the JMS spec, parameter validation must happen first: - Session: null topic now throws InvalidDestinationException, invalid selector now throws InvalidSelectorException - JMSContext: null topic now throws InvalidDestinationRuntimeException This fixes the TCK invalidDestinationExceptionTests and invalidSelectorExceptionTopicTests for Session, and the corresponding JMSContext tests. fix(client): always include JMSXDeliveryCount in message property names (#1720) getPropertyNames() only included JMSXDeliveryCount when redeliveryCounter != 0. Since JMSXDeliveryCount = redeliveryCounter + 1, it is always >= 1 on a received message and should always be enumerated. The TCK sets 17 custom properties and expects getPropertyNames() to return 18 (17 + JMSXDeliveryCount). * fix(tests): the spec requires InvalidDestinationException when passing null topic. So updating the call but we could instead adapt the expected exception. Anyways, this is a placeholder for the tests of the new features for JMS
1 parent e4d0149 commit 6871226

File tree

18 files changed

+755
-15
lines changed

18 files changed

+755
-15
lines changed

activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1362,7 +1362,7 @@ public QueueSession createQueueSession(boolean transacted, int acknowledgeMode)
13621362
*/
13631363
public void checkClientIDWasManuallySpecified() throws JMSException {
13641364
if (!userSpecifiedClientID) {
1365-
throw new JMSException("You cannot create a durable subscriber without specifying a unique clientID on a Connection");
1365+
throw new IllegalStateException("You cannot create a durable subscriber without specifying a unique clientID on a Connection");
13661366
}
13671367
}
13681368

activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionMetaData.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private ActiveMQConnectionMetaData() {
7373
*/
7474
@Override
7575
public String getJMSVersion() {
76-
return "1.1";
76+
return "3.1";
7777
}
7878

7979
/**
@@ -83,7 +83,7 @@ public String getJMSVersion() {
8383
*/
8484
@Override
8585
public int getJMSMajorVersion() {
86-
return 1;
86+
return 3;
8787
}
8888

8989
/**

activemq-client/src/main/java/org/apache/activemq/ActiveMQContext.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import jakarta.jms.Destination;
2626
import jakarta.jms.ExceptionListener;
2727
import jakarta.jms.IllegalStateRuntimeException;
28+
import jakarta.jms.InvalidDestinationRuntimeException;
2829
import jakarta.jms.JMSConsumer;
2930
import jakarta.jms.JMSContext;
3031
import jakarta.jms.JMSException;
@@ -442,21 +443,37 @@ public JMSConsumer createDurableConsumer(Topic topic, String name, String messag
442443

443444
@Override
444445
public JMSConsumer createSharedDurableConsumer(Topic topic, String name) {
446+
checkContextState();
447+
if (topic == null) {
448+
throw new InvalidDestinationRuntimeException("Topic cannot be null");
449+
}
445450
throw new UnsupportedOperationException("createSharedDurableConsumer(topic, name) is not supported");
446451
}
447452

448453
@Override
449454
public JMSConsumer createSharedDurableConsumer(Topic topic, String name, String messageSelector) {
450-
throw new UnsupportedOperationException("createDurableConsumer(topic, name, messageSelector) is not supported");
455+
checkContextState();
456+
if (topic == null) {
457+
throw new InvalidDestinationRuntimeException("Topic cannot be null");
458+
}
459+
throw new UnsupportedOperationException("createSharedDurableConsumer(topic, name, messageSelector) is not supported");
451460
}
452461

453462
@Override
454463
public JMSConsumer createSharedConsumer(Topic topic, String sharedSubscriptionName) {
464+
checkContextState();
465+
if (topic == null) {
466+
throw new InvalidDestinationRuntimeException("Topic cannot be null");
467+
}
455468
throw new UnsupportedOperationException("createSharedConsumer(topic, sharedSubscriptionName) is not supported");
456469
}
457470

458471
@Override
459472
public JMSConsumer createSharedConsumer(Topic topic, String sharedSubscriptionName, String messageSelector) {
473+
checkContextState();
474+
if (topic == null) {
475+
throw new InvalidDestinationRuntimeException("Topic cannot be null");
476+
}
460477
throw new UnsupportedOperationException("createSharedConsumer(topic, sharedSubscriptionName, messageSelector) is not supported");
461478
}
462479

activemq-client/src/main/java/org/apache/activemq/ActiveMQMessageProducer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ public void send(Destination destination, Message message, int deliveryMode, int
294294
checkClosed();
295295
if (destination == null) {
296296
if (info.getDestination() == null) {
297-
throw new UnsupportedOperationException("A destination must be specified.");
297+
throw new InvalidDestinationException("A destination must be specified.");
298298
}
299299
throw new InvalidDestinationException("Don't understand null destinations");
300300
}

activemq-client/src/main/java/org/apache/activemq/ActiveMQProducer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public class ActiveMQProducer implements JMSProducer {
6464

6565
@Override
6666
public JMSProducer send(Destination destination, Message message) {
67+
if (message == null) {
68+
throw new MessageFormatRuntimeException("Message must not be null");
69+
}
6770
try {
6871
if(this.correlationId != null) {
6972
message.setJMSCorrelationID(this.correlationId);

activemq-client/src/main/java/org/apache/activemq/ActiveMQSession.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import jakarta.jms.TopicSubscriber;
5959
import jakarta.jms.TransactionRolledBackException;
6060

61+
import org.apache.activemq.selector.SelectorParser;
6162
import org.apache.activemq.blob.BlobDownloader;
6263
import org.apache.activemq.blob.BlobTransferPolicy;
6364
import org.apache.activemq.blob.BlobUploader;
@@ -1386,11 +1387,22 @@ public Topic createTopic(String topicName) throws JMSException {
13861387

13871388
@Override
13881389
public MessageConsumer createSharedConsumer(Topic topic, String sharedSubscriptionName) throws JMSException {
1390+
checkClosed();
1391+
if (topic == null) {
1392+
throw new InvalidDestinationException("Topic cannot be null");
1393+
}
13891394
throw new UnsupportedOperationException("createSharedConsumer(Topic, sharedSubscriptionName) is not supported");
13901395
}
13911396

13921397
@Override
13931398
public MessageConsumer createSharedConsumer(Topic topic, String sharedSubscriptionName, String messageSelector) throws JMSException {
1399+
checkClosed();
1400+
if (topic == null) {
1401+
throw new InvalidDestinationException("Topic cannot be null");
1402+
}
1403+
if (messageSelector != null && !messageSelector.trim().isEmpty()) {
1404+
SelectorParser.parse(messageSelector);
1405+
}
13941406
throw new UnsupportedOperationException("createSharedConsumer(Topic, sharedSubscriptionName, messageSelector) is not supported");
13951407
}
13961408

@@ -1408,11 +1420,22 @@ public MessageConsumer createDurableConsumer(Topic topic, String name, String me
14081420

14091421
@Override
14101422
public MessageConsumer createSharedDurableConsumer(Topic topic, String name) throws JMSException {
1423+
checkClosed();
1424+
if (topic == null) {
1425+
throw new InvalidDestinationException("Topic cannot be null");
1426+
}
14111427
throw new UnsupportedOperationException("createSharedDurableConsumer(Topic, name) is not supported");
14121428
}
14131429

14141430
@Override
14151431
public MessageConsumer createSharedDurableConsumer(Topic topic, String name, String messageSelector) throws JMSException {
1432+
checkClosed();
1433+
if (topic == null) {
1434+
throw new InvalidDestinationException("Topic cannot be null");
1435+
}
1436+
if (messageSelector != null && !messageSelector.trim().isEmpty()) {
1437+
SelectorParser.parse(messageSelector);
1438+
}
14161439
throw new UnsupportedOperationException("createSharedDurableConsumer(Topic, name, messageSelector) is not supported");
14171440
}
14181441

activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,7 @@ public boolean propertyExists(String name) throws JMSException {
314314
public Enumeration getPropertyNames() throws JMSException {
315315
try {
316316
Vector<String> result = new Vector<String>(this.getProperties().keySet());
317-
if( getRedeliveryCounter()!=0 ) {
318-
result.add("JMSXDeliveryCount");
319-
}
317+
result.add("JMSXDeliveryCount");
320318
if( getGroupID()!=null ) {
321319
result.add("JMSXGroupID");
322320
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
jakarta-messaging-tck-*.zip
2+
target/
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
////
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
////
17+
= Jakarta Messaging TCK 3.1.0 Runner
18+
:toc:
19+
20+
This module runs the official https://jakarta.ee/specifications/messaging/3.1/[Jakarta Messaging 3.1.0 TCK] against Apache ActiveMQ using an embedded broker.
21+
22+
== Prerequisites
23+
24+
* **JDK 17 or 21** — the TCK harness does not compile on JDK 25 (`javax.rmi` removed)
25+
* **Apache Ant** — must be on `PATH` (e.g. `apt-get install ant`)
26+
* **curl** and **unzip** — for downloading and extracting the TCK
27+
28+
== How It Works
29+
30+
The module builds a shaded (fat) JAR containing the ActiveMQ broker, client, KahaDB store, and a custom `JNDIInitialContextFactory`.
31+
The `run_tck.sh` script then:
32+
33+
1. Downloads the TCK ZIP from `download.eclipse.org` (cached after first download)
34+
2. Verifies the SHA-256 checksum
35+
3. Extracts and patches the TCK source for JDK 17+ compatibility
36+
4. Builds the TCK test classes with Ant
37+
5. Runs all tests via `ant runclient`
38+
39+
Each forked test JVM starts an embedded ActiveMQ broker over `vm://localhost` (non-persistent, no JMX) through the JNDI factory.
40+
41+
== Build
42+
43+
[source,bash]
44+
----
45+
mvn clean package -pl activemq-tooling/activemq-jakarta-messaging-tck -am -DskipTests
46+
----
47+
48+
== Run the TCK
49+
50+
[source,bash]
51+
----
52+
mvn verify -pl activemq-tooling/activemq-jakarta-messaging-tck -Prun-tck
53+
----
54+
55+
If your default JDK is 25, point to a 17 or 21 installation:
56+
57+
[source,bash]
58+
----
59+
JAVA_HOME=/path/to/jdk17 mvn verify -pl activemq-tooling/activemq-jakarta-messaging-tck -Prun-tck
60+
----
61+
62+
Results are written to `target/tck-report/` and `target/tck-work/`.
63+
64+
== Configuration
65+
66+
`ts.jte`::
67+
TCK environment properties — classpaths, JNDI factory, timeout factor, test execution command.
68+
`jms.home` and `jms.classes` are appended automatically by the script.
69+
70+
`ts.jtx`::
71+
Test exclusion list.
72+
Add entries in the format `com/sun/ts/tests/jms/path/Class#method` to skip known-failing tests.
73+
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one or more
4+
contributor license agreements. See the NOTICE file distributed with
5+
this work for additional information regarding copyright ownership.
6+
The ASF licenses this file to You under the Apache License, Version 2.0
7+
(the "License"); you may not use this file except in compliance with
8+
the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
19+
20+
<modelVersion>4.0.0</modelVersion>
21+
22+
<parent>
23+
<groupId>org.apache.activemq.tooling</groupId>
24+
<artifactId>activemq-tooling</artifactId>
25+
<version>6.3.0-SNAPSHOT</version>
26+
</parent>
27+
28+
<artifactId>activemq-jakarta-messaging-tck</artifactId>
29+
<packaging>jar</packaging>
30+
<name>ActiveMQ :: Tooling :: Jakarta Messaging TCK Runner</name>
31+
<description>Runs the Jakarta Messaging 3.1.0 TCK against ActiveMQ</description>
32+
33+
<dependencies>
34+
<dependency>
35+
<groupId>org.apache.activemq</groupId>
36+
<artifactId>activemq-broker</artifactId>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.apache.activemq</groupId>
40+
<artifactId>activemq-client</artifactId>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.apache.activemq</groupId>
44+
<artifactId>activemq-kahadb-store</artifactId>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.apache.logging.log4j</groupId>
48+
<artifactId>log4j-slf4j2-impl</artifactId>
49+
</dependency>
50+
</dependencies>
51+
52+
<build>
53+
<plugins>
54+
<plugin>
55+
<groupId>org.apache.maven.plugins</groupId>
56+
<artifactId>maven-surefire-plugin</artifactId>
57+
<configuration>
58+
<skipTests>true</skipTests>
59+
</configuration>
60+
</plugin>
61+
<plugin>
62+
<groupId>org.apache.maven.plugins</groupId>
63+
<artifactId>maven-shade-plugin</artifactId>
64+
<executions>
65+
<execution>
66+
<phase>package</phase>
67+
<goals>
68+
<goal>shade</goal>
69+
</goals>
70+
<configuration>
71+
<filters>
72+
<filter>
73+
<artifact>*:*</artifact>
74+
<excludes>
75+
<exclude>META-INF/*.SF</exclude>
76+
<exclude>META-INF/*.DSA</exclude>
77+
<exclude>META-INF/*.RSA</exclude>
78+
</excludes>
79+
</filter>
80+
</filters>
81+
<transformers>
82+
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
83+
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
84+
<resource>META-INF/spring.handlers</resource>
85+
</transformer>
86+
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
87+
<resource>META-INF/spring.schemas</resource>
88+
</transformer>
89+
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
90+
<projectName>Apache ActiveMQ</projectName>
91+
</transformer>
92+
</transformers>
93+
</configuration>
94+
</execution>
95+
</executions>
96+
</plugin>
97+
</plugins>
98+
</build>
99+
100+
<profiles>
101+
<profile>
102+
<id>run-tck</id>
103+
<build>
104+
<plugins>
105+
<plugin>
106+
<groupId>org.codehaus.mojo</groupId>
107+
<artifactId>exec-maven-plugin</artifactId>
108+
<executions>
109+
<execution>
110+
<id>run-tck</id>
111+
<phase>verify</phase>
112+
<goals>
113+
<goal>exec</goal>
114+
</goals>
115+
<configuration>
116+
<executable>bash</executable>
117+
<workingDirectory>${project.basedir}</workingDirectory>
118+
<arguments>
119+
<argument>run_tck.sh</argument>
120+
<argument>ts.jte</argument>
121+
</arguments>
122+
</configuration>
123+
</execution>
124+
</executions>
125+
</plugin>
126+
</plugins>
127+
</build>
128+
</profile>
129+
</profiles>
130+
131+
</project>

0 commit comments

Comments
 (0)