Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ public class ActiveMQConnection implements Connection, TopicConnection, QueueCon
private RedeliveryPolicyMap redeliveryPolicyMap;
private MessageTransformer transformer;

/**
* If set to true, strict Jakarta Messaging 3.1 compliance is enforced.
* This strictly rejects non-standard property types such as Character, Map, and List.
*/
private boolean strictCompliance = false;

private boolean disableTimeStampsByDefault;
private boolean optimizedMessageDispatch = true;
private boolean copyMessageOnSend = true;
Expand Down Expand Up @@ -889,7 +895,7 @@ public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic, Str
int maxMessages) throws JMSException {
throw new UnsupportedOperationException("createSharedConnectionConsumer() is not supported");
}

// Properties
// -------------------------------------------------------------------------

Expand Down Expand Up @@ -1035,6 +1041,17 @@ public void setNestedMapAndListEnabled(boolean structuredMapsEnabled) {
this.nestedMapAndListEnabled = structuredMapsEnabled;
}

public boolean isStrictCompliance() {
return strictCompliance;
}

/**
* Sets whether strict Jakarta Messaging compliance is enforced for this connection.
*/
public void setStrictCompliance(boolean strictCompliance) {
this.strictCompliance = strictCompliance;
}

public boolean isExclusiveConsumer() {
return exclusiveConsumer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ public class ActiveMQConnectionFactory extends JNDIBaseStorable implements Conne
private BlobTransferPolicy blobTransferPolicy = new BlobTransferPolicy();
private MessageTransformer transformer;

/**
* If set to true, strict Jakarta Messaging 3.1 compliance is enforced.
* This strictly rejects non-standard property types such as Character, Map, and List.
*/
private boolean strictCompliance = false;

private boolean disableTimeStampsByDefault;
private boolean optimizedMessageDispatch = true;
private long optimizeAcknowledgeTimeOut = 300;
Expand Down Expand Up @@ -408,6 +414,10 @@ protected ActiveMQConnection createActiveMQConnection(String userName, String pa
protected ActiveMQConnection createActiveMQConnection(Transport transport, JMSStatsImpl stats) throws Exception {
ActiveMQConnection connection = new ActiveMQConnection(transport, getClientIdGenerator(),
getConnectionIdGenerator(), stats);

// Copy the compliance flags from the Factory to the Connection
connection.setStrictCompliance(isStrictCompliance());

return connection;
}

Expand Down Expand Up @@ -1000,6 +1010,17 @@ public void setNestedMapAndListEnabled(boolean structuredMapsEnabled) {
this.nestedMapAndListEnabled = structuredMapsEnabled;
}

public boolean isStrictCompliance() {
return strictCompliance;
}

/**
* If this flag is set then the connection follows strict Jakarta Messaging compliance.
*/
public void setStrictCompliance(boolean strictCompliance) {
this.strictCompliance = strictCompliance;
}

public String getClientIDPrefix() {
return clientIDPrefix;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,9 +529,18 @@ protected void checkValidObject(Object value) throws MessageFormatException {
boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long;
valid = valid || value instanceof Float || value instanceof Double || value instanceof Character || value instanceof String || value == null;

if (!valid) {
ActiveMQConnection conn = getConnection();

// strict rejection for Character
if (valid && conn != null && conn.isStrictCompliance() && value instanceof Character) {
throw new MessageFormatException("Character type not allowed under strict Jakarta 3.1 compliance");
}

ActiveMQConnection conn = getConnection();
if (!valid) {
// Check strict compliance at validation time without side-effecting the 'nested' flag
if (conn != null && conn.isStrictCompliance()) {
throw new MessageFormatException("Only objectified primitive objects and String types are allowed under strict Jakarta 3.1 compliance but was: " + value + " type: " + value.getClass());
}
// conn is null if we are in the broker rather than a JMS client
if (conn == null || conn.isNestedMapAndListEnabled()) {
if (!(value instanceof Map || value instanceof List)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* 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 org.apache.activemq.broker.BrokerService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import jakarta.jms.Connection;
import jakarta.jms.Message;
import jakarta.jms.MessageFormatException;
import jakarta.jms.Session;

import java.util.HashMap;
import java.util.Map;

import static org.apache.activemq.command.DataStructureTestSupport.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;

public class ActiveMQMessagePropertyTest {

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 testStrictComplianceMasterSwitch() throws Exception {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost");

// Turn on strict compliance
factory.setStrictCompliance(true);
// Explicitly turn on the legacy flag to prove strict mode overrides it
factory.setNestedMapAndListEnabled(true);

Connection connection = factory.createConnection();
connection.start();

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Message message = session.createMessage();

// Verify standard types work
message.setStringProperty("validString", "test"); // Should pass

// Verify Character is rejected
try {
message.setObjectProperty("invalidChar", 'A');
fail("Should have rejected Character under strict compliance");
} catch (MessageFormatException e) {
// Expected
}

// Verify Map is rejected (even though nestedMapAndListEnabled is true)
try {
Map<String, String> map = new HashMap<>();
message.setObjectProperty("invalidMap", map);
fail("Should have rejected Map under strict compliance");
} catch (MessageFormatException e) {
// Expected
}

connection.close();
}

@Test
public void testLegacyModeStillAllowsCharacter() throws Exception {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri);

// Default is strictCompliance = false
try (Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {

Message message = session.createMessage();
message.setObjectProperty("charProp", 'A'); // Should pass
assertEquals('A', message.getObjectProperty("charProp"));
}
}
}
Loading