This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 998f73add298 CAMEL-25073: camel-management - Start a CamelContext 
whose name has characters of JMX object names (item 1) (#27463)
998f73add298 is described below

commit 998f73add298e5367ba14cf9526bf1117265ad20
Author: allthingssecurity <[email protected]>
AuthorDate: Wed Oct 7 13:40:17 2026 +0530

    CAMEL-25073: camel-management - Start a CamelContext whose name has 
characters of JMX object names (item 1) (#27463)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../management/JmxManagementLifecycleStrategy.java |  57 ++++++-
 .../ManagedCamelContextNameObjectNameTest.java     | 181 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   9 +
 3 files changed, 244 insertions(+), 3 deletions(-)

diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
index fb4243c97a3c..be8b67f02ede 100644
--- 
a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
@@ -28,6 +28,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
 
 import javax.management.JMException;
+import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
@@ -199,13 +200,20 @@ public class JmxManagementLifecycleStrategy extends 
ServiceSupport implements Li
         if (managementName == null) {
             managementName = context.getManagementNameStrategy().getName();
         }
+        String sanitized = sanitizeManagementName(managementName);
+        if (sanitized != null && !sanitized.equals(managementName)) {
+            LOG.warn("The management name of CamelContext({}) contains 
characters that cannot be used in a JMX ObjectName."
+                     + " The management name: {} is used instead.",
+                    name, sanitized);
+            managementName = sanitized;
+        }
 
         try {
             boolean done = false;
             while (!done) {
                 ObjectName on = 
getManagementStrategy().getManagementObjectNameStrategy()
                         .getObjectNameForCamelContext(managementName, name);
-                boolean exists = getManagementStrategy().isManagedName(on);
+                boolean exists = isManagedContextName(on);
                 if (!exists) {
                     done = true;
                 } else {
@@ -301,10 +309,10 @@ public class JmxManagementLifecycleStrategy extends 
ServiceSupport implements Li
         String newName = null;
         while (!done) {
             // compute the next name
-            newName = strategy.getNextName();
+            newName = sanitizeManagementName(strategy.getNextName());
             ObjectName on
                     = 
getManagementStrategy().getManagementObjectNameStrategy().getObjectNameForCamelContext(newName,
 name);
-            done = !getManagementStrategy().isManagedName(on);
+            done = !isManagedContextName(on);
             if (LOG.isTraceEnabled()) {
                 LOG.trace("Using name: {} in ObjectName[{}] exists? {}", name, 
on, done);
             }
@@ -312,6 +320,49 @@ public class JmxManagementLifecycleStrategy extends 
ServiceSupport implements Li
         return newName;
     }
 
+    /**
+     * The management name is the value of the (unquoted) context key in every 
object name, so it must not contain a
+     * character that an unquoted value cannot have, or that makes the name a 
pattern: such characters are replaced with
+     * an underscore.
+     */
+    private static String sanitizeManagementName(String managementName) {
+        if (managementName == null) {
+            return null;
+        }
+        StringBuilder sb = null;
+        for (int i = 0; i < managementName.length(); i++) {
+            char ch = managementName.charAt(i);
+            if (ch == ',' || ch == '=' || ch == ':' || ch == '"' || ch == '*' 
|| ch == '?' || ch == '\n') {
+                if (sb == null) {
+                    sb = new StringBuilder(managementName);
+                }
+                sb.setCharAt(i, '_');
+            }
+        }
+        return sb != null ? sb.toString() : managementName;
+    }
+
+    /**
+     * Whether the context object name is registered, or another CamelContext 
already uses the same context key (with
+     * another name), as the object names of its routes, processors etc. would 
then clash.
+     */
+    private boolean isManagedContextName(ObjectName on) throws 
MalformedObjectNameException {
+        if (getManagementStrategy().isManagedName(on)) {
+            return true;
+        }
+        String key = 
on.getKeyProperty(DefaultManagementObjectNameStrategy.KEY_CONTEXT);
+        String type = 
on.getKeyProperty(DefaultManagementObjectNameStrategy.KEY_TYPE);
+        MBeanServer server = getManagementStrategy().getManagementAgent() != 
null
+                ? 
getManagementStrategy().getManagementAgent().getMBeanServer() : null;
+        if (key == null || type == null || server == null) {
+            return false;
+        }
+        ObjectName query = new ObjectName(
+                on.getDomain() + ":" + 
DefaultManagementObjectNameStrategy.KEY_CONTEXT + "=" + key + ","
+                                          + 
DefaultManagementObjectNameStrategy.KEY_TYPE + "=" + type + ",*");
+        return !server.queryNames(query, null).isEmpty();
+    }
+
     /**
      * After {@link CamelContext} has been enlisted in JMX using
      * {@link #onContextStarted(org.apache.camel.CamelContext)} then we can 
enlist any pre-registered services as well,
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedCamelContextNameObjectNameTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedCamelContextNameObjectNameTest.java
new file mode 100644
index 000000000000..51a2a7ac8edc
--- /dev/null
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedCamelContextNameObjectNameTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.camel.management;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.api.management.ManagedCamelContext;
+import org.apache.camel.api.management.mbean.ManagedRouteMBean;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The management name is the (unquoted) context key of every object name: a 
CamelContext name with characters that an
+ * unquoted ObjectName value cannot have must still start, and must not share 
its MBeans with another CamelContext.
+ */
+@DisabledOnOs(OS.AIX)
+class ManagedCamelContextNameObjectNameTest {
+
+    private final List<CamelContext> contexts = new ArrayList<>();
+
+    private CamelContext createCamelContext(String name, String 
managementPattern) throws Exception {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getCamelContextExtension().setName(name);
+        if (managementPattern != null) {
+            
context.getManagementNameStrategy().setNamePattern(managementPattern);
+        }
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("foo").to("mock:result");
+            }
+        });
+        contexts.add(context);
+        return context;
+    }
+
+    @AfterEach
+    void tearDown() {
+        contexts.forEach(CamelContext::stop);
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = { "my,camel", "my=camel", "my:camel", "my\"camel", 
"my*camel", "my?camel", "my\ncamel" })
+    void testContextNameWithObjectNameCharacters(String name) throws Exception 
{
+        CamelContext context = createCamelContext(name, null);
+        context.start();
+
+        assertTrue(context.getStatus().isStarted(), "Should be started");
+        assertEquals("my_camel", context.getManagementName());
+
+        MBeanServer mbeanServer = 
context.getManagementStrategy().getManagementAgent().getMBeanServer();
+        ObjectName on = ObjectName.getInstance(
+                "org.apache.camel:context=my_camel,type=context,name=" + 
ObjectName.quote(name));
+        assertTrue(mbeanServer.isRegistered(on), "The CamelContext MBean 
should be registered");
+        assertEquals(name, mbeanServer.getAttribute(on, "CamelId"));
+        assertTrue(mbeanServer.isRegistered(
+                
ObjectName.getInstance("org.apache.camel:context=my_camel,type=routes,name=\"foo\"")),
+                "The route MBean should be registered");
+
+        // the queries that build the context key from the management name 
work as for any other name
+        ManagedCamelContext managed = 
context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
+        ManagedRouteMBean route = managed.getManagedRoute("foo");
+        assertNotNull(route);
+        assertEquals(name, route.getCamelId());
+    }
+
+    @Test
+    void testSanitizedNameDoesNotShareMBeansWithExistingName() throws 
Exception {
+        CamelContext camel1 = createCamelContext("my_camel", null);
+        CamelContext camel2 = createCamelContext("my,camel", null);
+        camel1.start();
+        camel2.start();
+
+        assertSeparateMBeans(camel1, camel2);
+    }
+
+    @Test
+    void testExistingNameDoesNotShareMBeansWithSanitizedName() throws 
Exception {
+        CamelContext camel1 = createCamelContext("my,camel", null);
+        CamelContext camel2 = createCamelContext("my_camel", null);
+        camel1.start();
+        camel2.start();
+
+        assertSeparateMBeans(camel1, camel2);
+    }
+
+    @Test
+    void testNameOfCamelContextIsFreeNameOfAnother() throws Exception {
+        CamelContext camel1 = createCamelContext("foo", null);
+        CamelContext camel2 = createCamelContext("foo", null);
+        camel1.start();
+        camel2.start();
+
+        // the second foo got a free name such as foo-1, which is then the 
name of a third CamelContext
+        String freeName = camel2.getManagementName();
+        assertNotEquals("foo", freeName);
+        CamelContext camel3 = createCamelContext(freeName, null);
+        camel3.start();
+
+        assertSeparateMBeans(camel2, camel3);
+    }
+
+    @Test
+    void testManagementNamePatternOfTheAgentPicksFreeName() throws Exception {
+        // camel-main and Spring Boot set jmxManagementNamePattern (default 
#name#) on the management agent, not on the
+        // management name strategy: a clash still gets the next free name 
instead of a veto
+        CamelContext camel1 = createCamelContext("foo", null);
+        CamelContext camel2 = createCamelContext("foo", null);
+        
camel1.getManagementStrategy().getManagementAgent().setManagementNamePattern("#name#");
+        
camel2.getManagementStrategy().getManagementAgent().setManagementNamePattern("#name#");
+        camel1.start();
+        camel2.start();
+
+        assertEquals("foo", camel1.getManagementName());
+        assertSeparateMBeans(camel1, camel2);
+    }
+
+    @Test
+    void testManagementNamePatternOfTheStrategyWithoutCounterVetoes() throws 
Exception {
+        // a pattern without #counter# set on the management name strategy 
(such as managementNamePattern of the XML
+        // camelContext) is fixed: there is no next free name
+        CamelContext camel1 = createCamelContext("foo", "#name#");
+        CamelContext camel2 = createCamelContext("foo", "#name#");
+        camel1.start();
+
+        Exception e = assertThrows(Exception.class, camel2::start);
+        assertTrue(e.getCause().getMessage().contains("is already 
registered"), e.getCause().getMessage());
+    }
+
+    @Test
+    void testFixedManagementNameUsedByAnotherCamelContext() throws Exception {
+        CamelContext camel1 = createCamelContext("foo", "myFoo");
+        CamelContext camel2 = createCamelContext("bar", "myFoo");
+        camel1.start();
+
+        // another CamelContext uses the context key, so the object names of 
the routes would clash
+        Exception e = assertThrows(Exception.class, camel2::start);
+        assertTrue(e.getCause().getMessage().contains("is already 
registered"), e.getCause().getMessage());
+    }
+
+    private static void assertSeparateMBeans(CamelContext camel1, CamelContext 
camel2) {
+        assertNotEquals(camel1.getManagementName(), 
camel2.getManagementName());
+        for (CamelContext context : List.of(camel1, camel2)) {
+            ManagedCamelContext managed = 
context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
+            ManagedRouteMBean route = managed.getManagedRoute("foo");
+            assertNotNull(route, "The route MBean of " + context.getName() + " 
should be registered");
+            assertEquals(context.getName(), route.getCamelId(), "The route 
MBean should be the one of its CamelContext");
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 9c7d82c1a1f7..60e5212a3d24 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -719,6 +719,15 @@ should be reviewed.
 `InflightRepository.InflightExchange` and 
`AsyncProcessorAwaitManager.AwaitThread` gained a `getNodeSource()` method
 for the same value. Both are `default` methods returning `null`, so existing 
implementations continue to compile.
 
+A CamelContext name (or management name) with `, = : " * ?` or a line feed 
made the CamelContext fail to start with
+JMX. These characters are now replaced with `_` in the management name, the 
`context` key of the object names (the
+`CamelId` attribute keeps the name). A CamelContext whose management name is 
already the `context` key of another
+CamelContext now gets the next free management name, or fails to start with a 
fixed management name pattern; before,
+both started and the second one had no MBeans for its routes, processors and 
services. A fixed management name
+pattern is a pattern without `#counter#` set on the `ManagementNameStrategy` 
(such as the `managementNamePattern` of
+`<camelContext>` in Spring XML); the `jmxManagementNamePattern` option of 
camel-main and Spring Boot (default `#name#`)
+is the pattern of the management agent, and with it the next free management 
name is used.
+
 The `Redeliveries` statistic now also counts a redelivery attempt that 
succeeds, and it is only counted where the
 redelivery happened. For a processor it is the number of redelivery attempts 
of that processor. For a route it is the
 number of exchanges that were redelivered by a processor of that route, and 
for the CamelContext the number of

Reply via email to