From 1901be4c956f1b79aa605b0ed9aa4d02ad2c5e9d Mon Sep 17 00:00:00 2001 From: Chris Schwarzfischer Date: Fri, 15 May 2026 16:38:12 +0200 Subject: [PATCH 1/4] fix: Legacy queue prefix handling in `internalCreateQueueCompatibility`; add tests for auto-create behavior ARTEMIS-6064 --- .../artemis/jms/client/ActiveMQSession.java | 14 +- .../AutoCreateQueuesPrefixConflictTest.java | 237 ++++++++++++++++++ 2 files changed, 245 insertions(+), 6 deletions(-) create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java index 56935322dc0..6bc14f8c604 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java @@ -598,7 +598,7 @@ protected Queue internalCreateQueue(String queueName) throws ActiveMQException, } if (queue == null) { - queue = internalCreateQueueCompatibility("jms.queue." + queueName); + queue = internalCreateQueueCompatibility(PacketImpl.OLD_QUEUE_PREFIX + queueName); } if (queue == null) { throw new JMSException("There is no queue with name " + queueName); @@ -607,13 +607,15 @@ protected Queue internalCreateQueue(String queueName) throws ActiveMQException, } } + // HornetQ 1.x compatibility: locate an already-existing legacy-prefixed queue. Honoring + // isAutoCreateQueues here would let a broader address-settings match auto-create a queue + // under the legacy "jms.queue." name, so existence is the only signal we trust. protected ActiveMQQueue internalCreateQueueCompatibility(String queueName) throws ActiveMQException, JMSException { - ActiveMQQueue queue = lookupQueue(queueName, false); - - if (queue == null) { - queue = lookupQueue(queueName, true); + ActiveMQQueue queue = ActiveMQDestination.createQueue(queueName); + if (!session.queueQuery(queue.getSimpleAddress()).isExists()) { + return null; } - + queue.setName(queueName.substring(PacketImpl.OLD_QUEUE_PREFIX.length())); return queue; } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java new file mode 100644 index 00000000000..a5f9cf0be68 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java @@ -0,0 +1,237 @@ +/* + * 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.artemis.tests.integration.server; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Arrays; + +import javax.jms.Connection; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; + +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.settings.impl.AddressSettings; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Locks in the JMS-client behavior when two address-settings match the destination of a + * {@code createQueue} call: + * + * The producer is a plain Artemis CORE JMS client targeting {@code test.test2.somequeue} + * with no client-side prefix manipulation ({@code enable1xPrefixes} at its default {@code false}). + * Each test varies the {@code auto-create-queues} / {@code auto-create-addresses} flags on + * the two matches and asserts which (if any) broker-side queue is created. + * + *

Historical bug: when {@code auto-create-queues=false} on the specific match but + * {@code true} on the catch-all, {@code ActiveMQSession.internalCreateQueue} would fall back + * to its HornetQ 1.x compatibility path and call {@code lookupQueue("jms.queue." + name)}. + * That prefixed lookup matched the catch-all, so the JMS client handed the producer a + * {@code Queue} whose address was {@code jms.queue.test.test2.somequeue}, the producer sent + * to that address, and the broker auto-created the queue under the prefixed name — silently + * routing under a legacy-namespaced address despite the specific match disabling auto-create. + * + *

The fix in {@code ActiveMQSession.internalCreateQueueCompatibility} gates the legacy + * path on existence ({@code session.queueQuery(...).isExists()}) instead of on + * address-settings, so the prefixed fallback can no longer trigger auto-create. These tests + * pin the post-fix outcomes across all four combinations of the two flags. + */ +public class AutoCreateQueuesPrefixConflictTest extends ActiveMQTestBase { + + private static final SimpleString DLA = SimpleString.of("DLA"); + private static final String QUEUE_NAME = "test.test2.somequeue"; + private static final String PREFIXED_QUEUE_NAME = "jms.queue." + QUEUE_NAME; + private static final String DLQ_SUFFIX = ".DLQ"; + + private ActiveMQServer server; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + server = createServer(false); + server.getConfiguration().setAddressQueueScanPeriod(100); + } + + /** + * Specific match: auto-create FALSE. Catch-all: auto-create TRUE. The historical bug + * created {@code jms.queue.test.test2.somequeue} on the broker because the legacy lookup + * matched the catch-all. After the fix the specific match's auto-create=false wins: + * {@code internalCreateQueueCompatibility} returns null (no pre-existing prefixed queue), + * {@code internalCreateQueue} throws {@code JMSException}, and the broker creates nothing. + */ + @Test + public void testAutoCreateFalseOnSpecificMatch() throws Exception { + AddressSettings specific = baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false); + server.getAddressSettingsRepository().addMatch("test.test2.#", specific); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + + server.start(); + + runJmsScenarioAndDump("auto-create-queues=FALSE on test.test2.#"); + + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "legacy-prefixed queue must not be auto-created when auto-create-queues=false on the specific match"); + assertNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "un-prefixed queue must not exist either — auto-create=false on the specific match wins"); + } + + /** + * Both matches: auto-create TRUE. The specific match's queueQuery response carries + * {@code isAutoCreateQueues=true}, so the JMS client uses the un-prefixed name and the + * broker auto-creates {@code test.test2.somequeue}. No legacy fallback is triggered. + */ + @Test + public void testAutoCreateTrueOnSpecificMatch() throws Exception { + server.getAddressSettingsRepository().addMatch("test.test2.#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + + server.start(); + + runJmsScenarioAndDump("auto-create-queues=TRUE on test.test2.#"); + + assertNotNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "un-prefixed queue '" + QUEUE_NAME + "' should be auto-created"); + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "legacy-prefixed queue '" + PREFIXED_QUEUE_NAME + "' must not appear"); + } + + /** + * Specific match: auto-create TRUE. Catch-all: auto-create FALSE. The specific match + * applies to the un-prefixed address-settings lookup, so {@code isAutoCreateQueues=true} + * is returned, the JMS client uses the un-prefixed name, and the broker auto-creates + * {@code test.test2.somequeue}. The catch-all's auto-create=false is never consulted by + * the producer path. + */ + @Test + public void testCatchAllFalseSpecificTrue() throws Exception { + server.getAddressSettingsRepository().addMatch("test.test2.#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false)); + + server.start(); + + runJmsScenarioAndDump("catch-all FALSE / test.test2.# TRUE"); + + assertNotNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "un-prefixed queue '" + QUEUE_NAME + "' should be auto-created"); + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "legacy-prefixed queue '" + PREFIXED_QUEUE_NAME + "' must not appear"); + } + + /** + * Both matches: auto-create FALSE. The un-prefixed lookup returns + * {@code isExists=false}, {@code internalCreateQueueCompatibility} finds no pre-existing + * legacy-prefixed queue either, and {@code internalCreateQueue} throws + * {@code JMSException}. No queue is created. + */ + @Test + public void testBothMatchesFalse() throws Exception { + server.getAddressSettingsRepository().addMatch("test.test2.#", baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false)); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false)); + + server.start(); + + runJmsScenarioAndDump("both FALSE"); + + assertNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "'" + QUEUE_NAME + "' must not be auto-created when both matches disable it"); + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "'" + PREFIXED_QUEUE_NAME + "' must not appear either — both matches disable auto-create"); + } + + private static AddressSettings baseDlaSettings() { + return new AddressSettings() + .setAutoCreateDeadLetterResources(true) + .setDeadLetterAddress(DLA) + .setDeadLetterQueueSuffix(SimpleString.of(DLQ_SUFFIX)) + .setMaxDeliveryAttempts(1); + } + + /** + * Runs a CORE JMS produce/receive/rollback flow against {@link #QUEUE_NAME} so that the + * single message crosses {@code max-delivery-attempts=1} and triggers DLA resource + * auto-creation, then dumps the resulting queue/address inventory for diagnostic output. + * Any JMS exception is captured rather than propagated so the assertions can observe the + * broker-side state regardless of whether the producer flow succeeded. + */ + private void runJmsScenarioAndDump(String scenarioLabel) throws Exception { + Exception sendError = null; + ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://0"); + try (Connection connection = cf.createConnection()) { + Session session = connection.createSession(true, Session.SESSION_TRANSACTED); + Queue queue = session.createQueue(QUEUE_NAME); + MessageProducer producer = session.createProducer(queue); + producer.send(session.createTextMessage("hello")); + session.commit(); + + MessageConsumer consumer = session.createConsumer(queue); + connection.start(); + Message message = consumer.receive(2000); + if (message != null) { + session.rollback(); + // give the broker time to push to DLA & auto-create DLA resources + Thread.sleep(500); + } + } catch (Exception e) { + sendError = e; + } + + String[] queues = server.getActiveMQServerControl().getQueueNames(); + String[] addresses = server.getActiveMQServerControl().getAddressNames(); + Arrays.sort(queues); + Arrays.sort(addresses); + System.out.println("==== [" + scenarioLabel + "] ===="); + System.out.println(" queues: " + Arrays.toString(queues)); + System.out.println(" addresses: " + Arrays.toString(addresses)); + if (sendError != null) { + System.out.println(" JMS flow threw: " + sendError); + } + System.out.println("==== /[" + scenarioLabel + "] ===="); + + if (queues.length == 0 && sendError == null) { + fail("[" + scenarioLabel + "] no queues at all and no JMS error — test setup is broken"); + } + } +} From ac9ecad286ce4c6363589f6edef613e0fe3f5ca3 Mon Sep 17 00:00:00 2001 From: Chris Schwarzfischer Date: Fri, 15 May 2026 17:55:40 +0200 Subject: [PATCH 2/4] test: Refine and expand test coverage for legacy fallback in queue auto-create scenarios --- ...> AutoCreateQueuesLegacyFallbackTest.java} | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) rename tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/{AutoCreateQueuesPrefixConflictTest.java => AutoCreateQueuesLegacyFallbackTest.java} (69%) diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java similarity index 69% rename from tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java rename to tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java index a5f9cf0be68..d677e293ffe 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesPrefixConflictTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java @@ -38,31 +38,32 @@ import org.junit.jupiter.api.Test; /** - * Locks in the JMS-client behavior when two address-settings match the destination of a - * {@code createQueue} call: - *

- * The producer is a plain Artemis CORE JMS client targeting {@code test.test2.somequeue} - * with no client-side prefix manipulation ({@code enable1xPrefixes} at its default {@code false}). + * Locks in the JMS-client behavior when {@code ActiveMQSession.createQueue} cannot resolve + * the un-prefixed destination and falls back to the HornetQ 1.x compatibility path. The + * producer is a plain Artemis CORE JMS client targeting {@code test.test2.somequeue} with + * no client-side prefix manipulation ({@code enable1xPrefixes} at its default {@code false}). * Each test varies the {@code auto-create-queues} / {@code auto-create-addresses} flags on - * the two matches and asserts which (if any) broker-side queue is created. + * a specific {@code test.test2.#} match and on the catch-all {@code #} match and asserts + * which (if any) broker-side queue is created. * - *

Historical bug: when {@code auto-create-queues=false} on the specific match but - * {@code true} on the catch-all, {@code ActiveMQSession.internalCreateQueue} would fall back - * to its HornetQ 1.x compatibility path and call {@code lookupQueue("jms.queue." + name)}. - * That prefixed lookup matched the catch-all, so the JMS client handed the producer a - * {@code Queue} whose address was {@code jms.queue.test.test2.somequeue}, the producer sent - * to that address, and the broker auto-created the queue under the prefixed name — silently - * routing under a legacy-namespaced address despite the specific match disabling auto-create. + *

Historical bug: when the un-prefixed lookup failed (because the specific match had + * {@code auto-create-queues=false}), {@code ActiveMQSession.internalCreateQueue} fell back + * to {@code internalCreateQueueCompatibility}, which called + * {@code lookupQueue("jms.queue." + name)} and consulted address-settings for the prefixed + * name. The catch-all matched and reported auto-create=true, so the broker silently + * auto-created a queue under the legacy-prefixed address + * {@code jms.queue.test.test2.somequeue} — the producer ended up sending and the consumer + * receiving from a legacy-namespaced queue even though the specific match had auto-create + * disabled. The catch-all itself is not the problem; the bug is that the legacy fallback + * path triggers auto-creation under the prefixed name at all. * *

The fix in {@code ActiveMQSession.internalCreateQueueCompatibility} gates the legacy - * path on existence ({@code session.queueQuery(...).isExists()}) instead of on - * address-settings, so the prefixed fallback can no longer trigger auto-create. These tests - * pin the post-fix outcomes across all four combinations of the two flags. + * fallback on existence ({@code session.queueQuery(...).isExists()}) instead of on + * address-settings, so the fallback can only adopt a pre-existing legacy-prefixed queue + * and can no longer itself auto-create one. These tests pin the post-fix outcomes across + * all four combinations of the two flags. */ -public class AutoCreateQueuesPrefixConflictTest extends ActiveMQTestBase { +public class AutoCreateQueuesLegacyFallbackTest extends ActiveMQTestBase { private static final SimpleString DLA = SimpleString.of("DLA"); private static final String QUEUE_NAME = "test.test2.somequeue"; @@ -80,11 +81,13 @@ public void setUp() throws Exception { } /** - * Specific match: auto-create FALSE. Catch-all: auto-create TRUE. The historical bug - * created {@code jms.queue.test.test2.somequeue} on the broker because the legacy lookup - * matched the catch-all. After the fix the specific match's auto-create=false wins: - * {@code internalCreateQueueCompatibility} returns null (no pre-existing prefixed queue), - * {@code internalCreateQueue} throws {@code JMSException}, and the broker creates nothing. + * Specific match: auto-create FALSE. Catch-all: auto-create TRUE. This is the regression + * scenario. Pre-fix, the un-prefixed lookup failed, the legacy fallback consulted + * address-settings for {@code jms.queue.test.test2.somequeue}, the catch-all reported + * auto-create=true, and the broker created the prefixed queue. Post-fix, the fallback + * gates on existence: no pre-existing legacy queue → {@code internalCreateQueueCompatibility} + * returns null, {@code internalCreateQueue} throws {@code JMSException}, and the broker + * creates nothing. */ @Test public void testAutoCreateFalseOnSpecificMatch() throws Exception { @@ -101,15 +104,15 @@ public void testAutoCreateFalseOnSpecificMatch() throws Exception { runJmsScenarioAndDump("auto-create-queues=FALSE on test.test2.#"); assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), - "legacy-prefixed queue must not be auto-created when auto-create-queues=false on the specific match"); + "legacy fallback must not auto-create the prefixed queue when the un-prefixed match disables auto-create"); assertNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), - "un-prefixed queue must not exist either — auto-create=false on the specific match wins"); + "un-prefixed queue must not be auto-created either — auto-create-queues=false on the specific match"); } /** - * Both matches: auto-create TRUE. The specific match's queueQuery response carries - * {@code isAutoCreateQueues=true}, so the JMS client uses the un-prefixed name and the - * broker auto-creates {@code test.test2.somequeue}. No legacy fallback is triggered. + * Specific match: auto-create TRUE. Catch-all: auto-create TRUE. The un-prefixed lookup + * succeeds via the specific match, the JMS client never enters the legacy fallback path, + * and the broker auto-creates {@code test.test2.somequeue} under its expected name. */ @Test public void testAutoCreateTrueOnSpecificMatch() throws Exception { @@ -131,11 +134,10 @@ public void testAutoCreateTrueOnSpecificMatch() throws Exception { } /** - * Specific match: auto-create TRUE. Catch-all: auto-create FALSE. The specific match - * applies to the un-prefixed address-settings lookup, so {@code isAutoCreateQueues=true} - * is returned, the JMS client uses the un-prefixed name, and the broker auto-creates - * {@code test.test2.somequeue}. The catch-all's auto-create=false is never consulted by - * the producer path. + * Specific match: auto-create TRUE. Catch-all: auto-create FALSE. The un-prefixed lookup + * succeeds via the specific match and the broker auto-creates {@code test.test2.somequeue}. + * The legacy fallback path is never entered, so the catch-all's auto-create=false is + * irrelevant to the producer path. */ @Test public void testCatchAllFalseSpecificTrue() throws Exception { @@ -157,10 +159,10 @@ public void testCatchAllFalseSpecificTrue() throws Exception { } /** - * Both matches: auto-create FALSE. The un-prefixed lookup returns - * {@code isExists=false}, {@code internalCreateQueueCompatibility} finds no pre-existing - * legacy-prefixed queue either, and {@code internalCreateQueue} throws - * {@code JMSException}. No queue is created. + * Specific match and catch-all: both auto-create FALSE. The un-prefixed lookup returns + * {@code isExists=false}; the legacy fallback finds no pre-existing legacy-prefixed + * queue (and after the fix could not auto-create one anyway), so + * {@code internalCreateQueue} throws {@code JMSException} and nothing is created. */ @Test public void testBothMatchesFalse() throws Exception { @@ -176,9 +178,9 @@ public void testBothMatchesFalse() throws Exception { runJmsScenarioAndDump("both FALSE"); assertNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), - "'" + QUEUE_NAME + "' must not be auto-created when both matches disable it"); + "'" + QUEUE_NAME + "' must not be auto-created when auto-create is disabled"); assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), - "'" + PREFIXED_QUEUE_NAME + "' must not appear either — both matches disable auto-create"); + "'" + PREFIXED_QUEUE_NAME + "' must not be auto-created by the legacy fallback either"); } private static AddressSettings baseDlaSettings() { From 64b633fce4a8cefb1afe802e9112032b72e6aa98 Mon Sep 17 00:00:00 2001 From: Chris Schwarzfischer Date: Sat, 16 May 2026 11:04:19 +0200 Subject: [PATCH 3/4] test: Expand legacy fallback test coverage with additional scenarios to cover acceptor config being needed. --- .../AutoCreateQueuesLegacyFallbackTest.java | 127 +++++++++++------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java index d677e293ffe..95a2ef71052 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java @@ -30,6 +30,7 @@ import javax.jms.Session; import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; @@ -38,30 +39,10 @@ import org.junit.jupiter.api.Test; /** - * Locks in the JMS-client behavior when {@code ActiveMQSession.createQueue} cannot resolve - * the un-prefixed destination and falls back to the HornetQ 1.x compatibility path. The - * producer is a plain Artemis CORE JMS client targeting {@code test.test2.somequeue} with - * no client-side prefix manipulation ({@code enable1xPrefixes} at its default {@code false}). - * Each test varies the {@code auto-create-queues} / {@code auto-create-addresses} flags on - * a specific {@code test.test2.#} match and on the catch-all {@code #} match and asserts - * which (if any) broker-side queue is created. - * - *

Historical bug: when the un-prefixed lookup failed (because the specific match had - * {@code auto-create-queues=false}), {@code ActiveMQSession.internalCreateQueue} fell back - * to {@code internalCreateQueueCompatibility}, which called - * {@code lookupQueue("jms.queue." + name)} and consulted address-settings for the prefixed - * name. The catch-all matched and reported auto-create=true, so the broker silently - * auto-created a queue under the legacy-prefixed address - * {@code jms.queue.test.test2.somequeue} — the producer ended up sending and the consumer - * receiving from a legacy-namespaced queue even though the specific match had auto-create - * disabled. The catch-all itself is not the problem; the bug is that the legacy fallback - * path triggers auto-creation under the prefixed name at all. - * - *

The fix in {@code ActiveMQSession.internalCreateQueueCompatibility} gates the legacy - * fallback on existence ({@code session.queueQuery(...).isExists()}) instead of on - * address-settings, so the fallback can only adopt a pre-existing legacy-prefixed queue - * and can no longer itself auto-create one. These tests pin the post-fix outcomes across - * all four combinations of the two flags. + * Tests JMS-client behavior when {@code ActiveMQSession.createQueue} falls back to the + * HornetQ 1.x compatibility path. Tests vary {@code auto-create-queues} and + * {@code auto-create-addresses} flags on specific and catch-all matches to verify the + * fix that prevents the legacy fallback from auto-creating prefixed queues. */ public class AutoCreateQueuesLegacyFallbackTest extends ActiveMQTestBase { @@ -70,6 +51,10 @@ public class AutoCreateQueuesLegacyFallbackTest extends ActiveMQTestBase { private static final String PREFIXED_QUEUE_NAME = "jms.queue." + QUEUE_NAME; private static final String DLQ_SUFFIX = ".DLQ"; + private static final int COMPAT_ACCEPTOR_PORT = TransportConstants.DEFAULT_PORT + 1; + private static final String COMPAT_ACCEPTOR_URL = + "tcp://localhost:" + COMPAT_ACCEPTOR_PORT + "?anycastPrefix=jms.queue.;multicastPrefix=jms.topic."; + private ActiveMQServer server; @Override @@ -81,13 +66,9 @@ public void setUp() throws Exception { } /** - * Specific match: auto-create FALSE. Catch-all: auto-create TRUE. This is the regression - * scenario. Pre-fix, the un-prefixed lookup failed, the legacy fallback consulted - * address-settings for {@code jms.queue.test.test2.somequeue}, the catch-all reported - * auto-create=true, and the broker created the prefixed queue. Post-fix, the fallback - * gates on existence: no pre-existing legacy queue → {@code internalCreateQueueCompatibility} - * returns null, {@code internalCreateQueue} throws {@code JMSException}, and the broker - * creates nothing. + * Specific match: auto-create FALSE. Catch-all: auto-create TRUE. Verifies the legacy + * fallback no longer auto-creates the prefixed queue when the specific match disables + * auto-create. */ @Test public void testAutoCreateFalseOnSpecificMatch() throws Exception { @@ -110,9 +91,8 @@ public void testAutoCreateFalseOnSpecificMatch() throws Exception { } /** - * Specific match: auto-create TRUE. Catch-all: auto-create TRUE. The un-prefixed lookup - * succeeds via the specific match, the JMS client never enters the legacy fallback path, - * and the broker auto-creates {@code test.test2.somequeue} under its expected name. + * Specific match: auto-create TRUE. Catch-all: auto-create TRUE. The un-prefixed queue + * is auto-created normally without entering the legacy fallback path. */ @Test public void testAutoCreateTrueOnSpecificMatch() throws Exception { @@ -134,10 +114,8 @@ public void testAutoCreateTrueOnSpecificMatch() throws Exception { } /** - * Specific match: auto-create TRUE. Catch-all: auto-create FALSE. The un-prefixed lookup - * succeeds via the specific match and the broker auto-creates {@code test.test2.somequeue}. - * The legacy fallback path is never entered, so the catch-all's auto-create=false is - * irrelevant to the producer path. + * Specific match: auto-create TRUE. Catch-all: auto-create FALSE. The un-prefixed queue + * is auto-created via the specific match; legacy fallback is never entered. */ @Test public void testCatchAllFalseSpecificTrue() throws Exception { @@ -159,10 +137,8 @@ public void testCatchAllFalseSpecificTrue() throws Exception { } /** - * Specific match and catch-all: both auto-create FALSE. The un-prefixed lookup returns - * {@code isExists=false}; the legacy fallback finds no pre-existing legacy-prefixed - * queue (and after the fix could not auto-create one anyway), so - * {@code internalCreateQueue} throws {@code JMSException} and nothing is created. + * Specific match and catch-all: both auto-create FALSE. No queue is created by either + * the normal or legacy fallback path. */ @Test public void testBothMatchesFalse() throws Exception { @@ -183,6 +159,58 @@ public void testBothMatchesFalse() throws Exception { "'" + PREFIXED_QUEUE_NAME + "' must not be auto-created by the legacy fallback either"); } + /** + * Tests legacy fallback through an acceptor with {@code anycastPrefix=jms.queue.}. + * Verifies the fix doesn't change behavior for deployments with the acceptor-side + * workaround. + */ + @Test + public void testLegacyFallbackThroughAcceptorWithAnycastPrefix() throws Exception { + server.getAddressSettingsRepository().addMatch("test.test2.#", baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false)); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + + server.getConfiguration().addAcceptorConfiguration("compat", COMPAT_ACCEPTOR_URL); + server.start(); + + runJmsScenarioAndDump("acceptor anycastPrefix=jms.queue. + specific FALSE", + "tcp://localhost:" + COMPAT_ACCEPTOR_PORT, QUEUE_NAME); + + assertNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "un-prefixed queue must not be auto-created — specific match disables auto-create"); + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "legacy-prefixed queue must not appear — broker's anycastPrefix already normalizes the address"); + } + + /** + * Verifies legacy-1.x JMS clients ({@code enable1xPrefixes=true}) with acceptor + * {@code anycastPrefix=jms.queue.} are unaffected by the fix. The normal lookup + * succeeds and auto-creates the un-prefixed queue. + */ + @Test + public void testAnycastPrefixClientStillAutoCreates() throws Exception { + server.getAddressSettingsRepository().addMatch("test.test2.#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(true) + .setAutoCreateAddresses(true)); + + server.getConfiguration().addAcceptorConfiguration("compat", COMPAT_ACCEPTOR_URL); + server.start(); + + runJmsScenarioAndDump("acceptor anycastPrefix + client enable1xPrefixes", + "tcp://localhost:" + COMPAT_ACCEPTOR_PORT + "?enable1xPrefixes=true", QUEUE_NAME); + + assertNotNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "un-prefixed queue '" + QUEUE_NAME + "' should be auto-created — prefix is stripped at the broker"); + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "legacy-prefixed queue must not appear — the acceptor normalizes back to the un-prefixed name"); + } + private static AddressSettings baseDlaSettings() { return new AddressSettings() .setAutoCreateDeadLetterResources(true) @@ -192,18 +220,19 @@ private static AddressSettings baseDlaSettings() { } /** - * Runs a CORE JMS produce/receive/rollback flow against {@link #QUEUE_NAME} so that the - * single message crosses {@code max-delivery-attempts=1} and triggers DLA resource - * auto-creation, then dumps the resulting queue/address inventory for diagnostic output. - * Any JMS exception is captured rather than propagated so the assertions can observe the - * broker-side state regardless of whether the producer flow succeeded. + * Runs a JMS produce/receive/rollback flow to trigger DLA resource auto-creation and + * dumps the resulting queue/address inventory. Captures JMS exceptions for diagnostics. */ private void runJmsScenarioAndDump(String scenarioLabel) throws Exception { + runJmsScenarioAndDump(scenarioLabel, "vm://0", QUEUE_NAME); + } + + private void runJmsScenarioAndDump(String scenarioLabel, String brokerUrl, String jmsQueueName) throws Exception { Exception sendError = null; - ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://0"); + ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl); try (Connection connection = cf.createConnection()) { Session session = connection.createSession(true, Session.SESSION_TRANSACTED); - Queue queue = session.createQueue(QUEUE_NAME); + Queue queue = session.createQueue(jmsQueueName); MessageProducer producer = session.createProducer(queue); producer.send(session.createTextMessage("hello")); session.commit(); From 7c485df03ff2732fac60ae4ea48d71cb3bc5a376 Mon Sep 17 00:00:00 2001 From: Chris Schwarzfischer Date: Sat, 16 May 2026 11:27:51 +0200 Subject: [PATCH 4/4] test: Add test for legacy fallback with disabled auto-create scenarios on prefixed acceptor configuration --- .../AutoCreateQueuesLegacyFallbackTest.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java index 95a2ef71052..bde980aa4b2 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/server/AutoCreateQueuesLegacyFallbackTest.java @@ -186,9 +186,12 @@ public void testLegacyFallbackThroughAcceptorWithAnycastPrefix() throws Exceptio } /** - * Verifies legacy-1.x JMS clients ({@code enable1xPrefixes=true}) with acceptor - * {@code anycastPrefix=jms.queue.} are unaffected by the fix. The normal lookup - * succeeds and auto-creates the un-prefixed queue. + * Happy-path cover for legacy-1.x clients ({@code enable1xPrefixes=true}) on an + * acceptor with {@code anycastPrefix=jms.queue.}. The normal lookup succeeds via + * {@code isAutoCreateQueues=true}, so the legacy fallback is NOT entered here — + * the compatibility-path fix is not exercised by this test. Compat-path coverage + * in the same client configuration lives in + * {@link #testAnycastPrefixClientHitsCompatPathCleanly}. */ @Test public void testAnycastPrefixClientStillAutoCreates() throws Exception { @@ -211,6 +214,36 @@ public void testAnycastPrefixClientStillAutoCreates() throws Exception { "legacy-prefixed queue must not appear — the acceptor normalizes back to the un-prefixed name"); } + /** + * Forces the legacy fallback for {@code enable1xPrefixes=true} clients on an + * {@code anycastPrefix=jms.queue.} acceptor: both matches set auto-create=false so + * both regular {@code lookupQueue} calls return null and + * {@code internalCreateQueueCompatibility} runs. The post-fix compat queueQuery + * is normalized by the broker back to the un-prefixed name, finds nothing, and + * the JMS call throws cleanly. Guards against accidental auto-creation when the + * compat path is exercised in this client configuration. + */ + @Test + public void testAnycastPrefixClientHitsCompatPathCleanly() throws Exception { + server.getAddressSettingsRepository().addMatch("test.test2.#", baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false)); + server.getAddressSettingsRepository().addMatch("#", baseDlaSettings() + .setAutoCreateQueues(false) + .setAutoCreateAddresses(false)); + + server.getConfiguration().addAcceptorConfiguration("compat", COMPAT_ACCEPTOR_URL); + server.start(); + + runJmsScenarioAndDump("acceptor anycastPrefix + client enable1xPrefixes + all FALSE", + "tcp://localhost:" + COMPAT_ACCEPTOR_PORT + "?enable1xPrefixes=true", QUEUE_NAME); + + assertNull(server.locateQueue(SimpleString.of(QUEUE_NAME)), + "un-prefixed queue must not be auto-created — all auto-create disabled"); + assertNull(server.locateQueue(SimpleString.of(PREFIXED_QUEUE_NAME)), + "legacy-prefixed queue must not appear — compat path returns null cleanly"); + } + private static AddressSettings baseDlaSettings() { return new AddressSettings() .setAutoCreateDeadLetterResources(true)