diff --git a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java index 3a02d074c66..82e1e2d10d8 100644 --- a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java +++ b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java @@ -501,6 +501,19 @@ public void setObjectProperty(String name, Object value, boolean checkReadOnly) checkValidObject(value); value = convertScheduled(name, value); + + // Strict Compliance Check For Provider-Set JMSX Properties + ActiveMQConnection conn = getConnection(); + if (conn != null && conn.isStrictCompliance()) { + if ("JMSXDeliveryCount".equals(name) || + "JMSXRcvTimestamp".equals(name) || + "JMSXState".equals(name) || + "JMSXProducerTXID".equals(name) || + "JMSXConsumerTXID".equals(name)) { + throw new JMSException("Provider-set JMSX property '" + name + "' cannot be set by a client under strict compliance."); + } + } + PropertySetter setter = JMS_PROPERTY_SETERS.get(name); if (setter != null && value != null) { diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/StrictComplianceProviderJMSXPropertyTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/StrictComplianceProviderJMSXPropertyTest.java new file mode 100644 index 00000000000..223a68483d9 --- /dev/null +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/StrictComplianceProviderJMSXPropertyTest.java @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq; + +import jakarta.jms.Connection; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.broker.BrokerService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +public class StrictComplianceProviderJMSXPropertyTest { + + private BrokerService broker; + private String connectionUri; + + @Before + public void setUp() throws Exception { + broker = new BrokerService(); + broker.setPersistent(false); + broker.setUseJmx(false); + broker.addConnector("vm://localhost"); + broker.start(); + broker.waitUntilStarted(); + connectionUri = broker.getTransportConnectors().get(0).getPublishableConnectString(); + } + + @After + public void tearDown() throws Exception { + if (broker != null) { + broker.stop(); + broker.waitUntilStopped(); + } + } + + @Test + public void testLegacyModeAllowsProviderSetJMSXProperties() throws Exception { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); + + // Legacy mode (default) + factory.setStrictCompliance(false); + + try (Connection connection = factory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + + connection.start(); + Message message = session.createMessage(); + + try { + // ActiveMQ allows clients to overwrite the delivery count + message.setIntProperty("JMSXDeliveryCount", 5); + assertEquals(5, message.getIntProperty("JMSXDeliveryCount")); + // PASS: Should not throw an exception in legacy mode + } catch (Exception e) { + fail("Legacy mode should not restrict setting provider-only JMSX properties"); + } + } + } + + @Test + public void testStrictModeRejectsProviderSetJMSXProperties() throws Exception { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); + + // Enforce Jakarta 3.1 compliance + factory.setStrictCompliance(true); + + try (Connection connection = factory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + + connection.start(); + Message message = session.createMessage(); + + String[] providerOnlyProperties = { + "JMSXDeliveryCount", + "JMSXRcvTimestamp", + "JMSXState", + "JMSXProducerTXID", + "JMSXConsumerTXID" + }; + + for (String property : providerOnlyProperties) { + try { + // Attempting to set these should fail + message.setObjectProperty(property, 1); + fail("Strict mode must reject client attempt to set provider-only property: " + property); + } catch (JMSException e) { + // PASS: Expected Jakarta Messaging 3.1 behavior + } + } + } + } + + @Test + public void testStrictModeAllowsClientSetJMSXProperties() throws Exception { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); + + // Enforce Jakarta 3.1 compliance + factory.setStrictCompliance(true); + + try (Connection connection = factory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + + connection.start(); + Message message = session.createMessage(); + + try { + // The spec explicitly allows clients to set these specific JMSX properties + message.setStringProperty("JMSXGroupID", "Group-A"); + message.setIntProperty("JMSXGroupSeq", 1); + + assertEquals("Group-A", message.getStringProperty("JMSXGroupID")); + assertEquals(1, message.getIntProperty("JMSXGroupSeq")); + // PASS: Valid client-set properties succeeded + } catch (JMSException e) { + fail("Strict mode incorrectly rejected a valid client-set JMSX property"); + } + } + } +} \ No newline at end of file diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java index 50f61d35dea..a7bbacfc542 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java @@ -165,8 +165,15 @@ public void onMessage(Message message) { assertTrue(Wait.waitFor(new Condition() { @Override public boolean isSatisified() throws Exception { - // The number of message that remain is due to the exclude queue - return receivedMessages.size() == MESSAGE_COUNT; + // Check if messages arrived at the consumer + boolean messagesReceived = receivedMessages.size() == MESSAGE_COUNT; + + // Check if the asynchronous broker statistics have settled + long localDequeues = localBroker.getDestination(includedDestination).getDestinationStatistics().getDequeues().getCount(); + long remoteEnqueues = remoteBroker.getDestination(includedDestination).getDestinationStatistics().getEnqueues().getCount(); + + // The test can only proceed when BOTH the messages are received and the stats match + return messagesReceived && localDequeues == MESSAGE_COUNT && remoteEnqueues == MESSAGE_COUNT; } }, 30000, 500));