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 56215ac16a8c CAMEL-25073: camel-management - Keep the MBean of a
processor shared by routes while another route uses it (item 2) (#27464)
56215ac16a8c is described below
commit 56215ac16a8cdcb9189a58530e51fd9a951624a6
Author: allthingssecurity <[email protected]>
AuthorDate: Wed Oct 7 15:57:00 2026 +0530
CAMEL-25073: camel-management - Keep the MBean of a processor shared by
routes while another route uses it (item 2) (#27464)
Outputs of a context scoped onException/onCompletion and route
configuration onException share one processor MBean across routes. Removing one
route unregistered it while other routes still used it. The MBean is now kept
while another route uses it and re-attached as needed (CAMEL-25073 item 2).
Closes #27464
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../InstrumentationInterceptStrategy.java | 24 +++-
.../management/JmxManagementLifecycleStrategy.java | 105 ++++++++++++++---
.../InstrumentationInterceptStrategyTest.java | 34 ++++++
...gedRouteRemoveContextScopedOnExceptionTest.java | 124 +++++++++++++++++++++
.../ManagedRouteRemoveSharedProcessorTest.java | 85 ++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 4 +
6 files changed, 354 insertions(+), 22 deletions(-)
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/InstrumentationInterceptStrategy.java
b/core/camel-management/src/main/java/org/apache/camel/management/InstrumentationInterceptStrategy.java
index bae6d03b0139..681d7d4a14ca 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/InstrumentationInterceptStrategy.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/InstrumentationInterceptStrategy.java
@@ -20,9 +20,10 @@ import java.util.Map;
import org.apache.camel.NamedNode;
import org.apache.camel.Processor;
+import org.apache.camel.Route;
import org.apache.camel.management.mbean.ManagedPerformanceCounter;
import org.apache.camel.spi.ManagementInterceptStrategy;
-import org.apache.camel.util.KeyValueHolder;
+import org.apache.camel.util.ObjectHelper;
/**
* This strategy class wraps targeted processors with a {@link
InstrumentationProcessor}. Each InstrumentationProcessor
@@ -33,13 +34,26 @@ import org.apache.camel.util.KeyValueHolder;
*/
public class InstrumentationInterceptStrategy implements
ManagementInterceptStrategy {
+ /**
+ * A processor that has been wrapped for performance counters.
+ *
+ * @param definition the definition the processor was
created from, which can be shared by several
+ * routes (such as the outputs of a
context scoped onException)
+ * @param instrumentationProcessor the processor that wraps it
+ * @param route the route the processor belongs to
+ */
+ public record WrappedProcessor(NamedNode definition,
InstrumentationProcessor<?> instrumentationProcessor, Route route) {
+ }
+
private final Map<NamedNode, PerformanceCounter> registeredCounters;
- private final Map<Processor, KeyValueHolder<NamedNode,
InstrumentationProcessor<?>>> wrappedProcessors;
+ private final Map<Processor, WrappedProcessor> wrappedProcessors;
+ private final Route route;
public InstrumentationInterceptStrategy(Map<NamedNode, PerformanceCounter>
registeredCounters,
- Map<Processor,
KeyValueHolder<NamedNode, InstrumentationProcessor<?>>> wrappedProcessors) {
+ Map<Processor, WrappedProcessor>
wrappedProcessors, Route route) {
this.registeredCounters = registeredCounters;
this.wrappedProcessors = wrappedProcessors;
+ this.route = ObjectHelper.notNull(route, "route");
}
@Override
@@ -55,9 +69,7 @@ public class InstrumentationInterceptStrategy implements
ManagementInterceptStra
if (counter != null) {
// add it to the mapping of wrappers so we can later change it to a
// decorated counter when we register the processor
- KeyValueHolder<NamedNode, InstrumentationProcessor<?>> holder
- = new KeyValueHolder<>(definition,
instrumentationProcessor);
- wrappedProcessors.put(target, holder);
+ wrappedProcessors.put(target, new WrappedProcessor(definition,
instrumentationProcessor, route));
}
return instrumentationProcessor;
}
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 2b0ecc33282b..6cea6d06718a 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
@@ -57,6 +57,7 @@ import org.apache.camel.cluster.CamelClusterService;
import org.apache.camel.health.HealthCheckRegistry;
import org.apache.camel.impl.debugger.BacklogTracer;
import org.apache.camel.impl.debugger.DefaultBacklogDebugger;
+import
org.apache.camel.management.InstrumentationInterceptStrategy.WrappedProcessor;
import org.apache.camel.management.mbean.ManagedAsyncProcessorAwaitManager;
import org.apache.camel.management.mbean.ManagedBacklogDebugger;
import org.apache.camel.management.mbean.ManagedBacklogTracer;
@@ -70,6 +71,7 @@ import
org.apache.camel.management.mbean.ManagedEndpointServiceRegistry;
import org.apache.camel.management.mbean.ManagedErrorRegistry;
import org.apache.camel.management.mbean.ManagedExchangeFactoryManager;
import org.apache.camel.management.mbean.ManagedInflightRepository;
+import org.apache.camel.management.mbean.ManagedProcessor;
import org.apache.camel.management.mbean.ManagedProducerCache;
import org.apache.camel.management.mbean.ManagedRestRegistry;
import org.apache.camel.management.mbean.ManagedRoute;
@@ -131,7 +133,6 @@ import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.support.task.TaskManagerRegistry;
import org.apache.camel.throttling.ThrottlingExceptionRoutePolicy;
import org.apache.camel.throttling.ThrottlingInflightRoutePolicy;
-import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -148,8 +149,9 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
// the wrapped processors is for performance counters, which are in use
for the created routes
// when a route is removed, we should remove the associated processors
from this map
- private final Map<Processor, KeyValueHolder<NamedNode,
InstrumentationProcessor<?>>> wrappedProcessors = new HashMap<>();
- // the managed object of a processor (by its definition), which is reused
when its route is started again
+ private final Map<Processor, WrappedProcessor> wrappedProcessors = new
HashMap<>();
+ // the managed object of a processor (by its definition), which is reused
when its route is started again,
+ // and shared by the routes that use the same definition (such as the
outputs of a context scoped onException)
private final Map<NamedNode, Object> managedProcessors = new HashMap<>();
private final
List<java.util.function.Consumer<JmxManagementLifecycleStrategy>> preServices =
new ArrayList<>();
private final TimerListenerManager loadTimer = new ManagedLoadTimer();
@@ -623,6 +625,13 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
Object managedObject = getManagedObjectForService(context, service,
route);
if (managedObject != null) {
+ Map.Entry<Processor, WrappedProcessor> other
+ = service instanceof Processor processor ?
getProcessorOfOtherRoute(processor) : null;
+ if (other != null) {
+ // the definition is shared with another route that still uses
the mbean, so keep it
+ reattachProcessor(managedObject, other);
+ return;
+ }
try {
unmanageObject(managedObject);
} catch (Exception e) {
@@ -630,9 +639,58 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
}
}
if (service instanceof Processor processor) {
- KeyValueHolder<NamedNode, InstrumentationProcessor<?>> holder =
wrappedProcessors.get(processor);
+ WrappedProcessor holder = wrappedProcessors.get(processor);
if (holder != null) {
- managedProcessors.remove(holder.getKey());
+ managedProcessors.remove(holder.definition());
+ }
+ }
+ }
+
+ /**
+ * Gets a processor of another route that is created from the same
definition as the given processor, such as the
+ * outputs of a context scoped onException, which is the same definition
in every route.
+ */
+ private Map.Entry<Processor, WrappedProcessor>
getProcessorOfOtherRoute(Processor processor) {
+ WrappedProcessor holder = wrappedProcessors.get(processor);
+ if (holder == null || isRouteDefinition(holder)) {
+ return null;
+ }
+ String routeId = holder.route().getId();
+ for (Map.Entry<Processor, WrappedProcessor> entry :
wrappedProcessors.entrySet()) {
+ WrappedProcessor other = entry.getValue();
+ if (other.definition() == holder.definition() &&
!routeId.equals(other.route().getId())) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Whether the definition of the wrapped processor belongs to the route of
the processor (and not to several routes)
+ */
+ private static boolean isRouteDefinition(WrappedProcessor holder) {
+ RouteDefinition def =
ProcessorDefinitionHelper.getRoute(holder.definition());
+ return def != null && holder.route().getId().equals(def.getId());
+ }
+
+ /**
+ * Attaches the managed object of a processor, whose route is being
removed, to the processor of another route that
+ * uses the same definition.
+ */
+ private void reattachProcessor(Object managedObject, Map.Entry<Processor,
WrappedProcessor> other) {
+ if (managedObject instanceof ManagedProcessor mp) {
+ mp.setProcessor(other.getKey());
+ Route route = other.getValue().route();
+ if (mp.getRoute() != route) {
+ // register the mbean again so it shows (and is listed under)
the route that still uses it
+ try {
+ unmanageObject(mp);
+ mp.setRoute(route);
+ manageObject(mp);
+ } catch (Exception e) {
+ LOG.warn("Could not register processor: {} as Processor
MBean of route: {}", mp.getProcessorId(),
+ route.getId(), e);
+ }
}
}
}
@@ -748,7 +806,7 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
// a bit of magic here as the processors we want to manage have
already been registered
// in the wrapped processors map when Camel have instrumented the
route on route initialization
// so the idea is now to only manage the processors from the map
- KeyValueHolder<NamedNode, InstrumentationProcessor<?>> holder =
wrappedProcessors.get(processor);
+ WrappedProcessor holder = wrappedProcessors.get(processor);
if (holder == null) {
// skip as it's not a well known processor we want to manage
anyway, such as Channel/UnitOfWork/Pipeline etc.
return null;
@@ -756,20 +814,21 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
// reuse the managed object of the processor, as it is already
registered when its route is started again
// (otherwise the statistics would be counted on a new object that is
not the registered MBean)
- Object managedObject = managedProcessors.get(holder.getKey());
+ Object managedObject = managedProcessors.get(holder.definition());
if (managedObject == null) {
// get the managed object as it can be a specialized type such as
a Delayer/Throttler etc.
managedObject
- =
getManagementObjectStrategy().getManagedObjectForProcessor(context, processor,
holder.getKey(), route);
+ =
getManagementObjectStrategy().getManagedObjectForProcessor(context, processor,
holder.definition(),
+ route);
if (managedObject != null) {
- managedProcessors.put(holder.getKey(), managedObject);
+ managedProcessors.put(holder.definition(), managedObject);
}
}
// only manage if we have a name for it as otherwise we do not want to
manage it anyway
if (managedObject != null) {
// is it a performance counter then we need to set our counter
if (managedObject instanceof PerformanceCounter) {
- InstrumentationProcessor<?> counter = holder.getValue();
+ InstrumentationProcessor<?> counter =
holder.instrumentationProcessor();
if (counter != null) {
// change counter to us
counter.setCounter(managedObject);
@@ -1040,7 +1099,8 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
// set this managed intercept strategy that executes the JMX
instrumentation for performance metrics
// so our registered counters can be used for fine-grained performance
instrumentation
- route.setManagementInterceptStrategy(new
InstrumentationInterceptStrategy(registeredCounters, wrappedProcessors));
+ route.setManagementInterceptStrategy(
+ new InstrumentationInterceptStrategy(registeredCounters,
wrappedProcessors, route));
}
/**
@@ -1052,18 +1112,31 @@ public class JmxManagementLifecycleStrategy extends
ServiceSupport implements Li
*/
private void removeWrappedProcessorsForRoutes(Collection<Route> routes) {
// loop the routes, and remove the route associated wrapped
processors, as they are no longer in use
+ // (by the route they were created for, as a definition can be shared
by several routes, such as the outputs
+ // of a context scoped onException, whose parent is not a route)
for (Route route : routes) {
String id = route.getId();
- Iterator<KeyValueHolder<NamedNode, InstrumentationProcessor<?>>>
it = wrappedProcessors.values().iterator();
+ Set<NamedNode> shared = new HashSet<>();
+ Iterator<WrappedProcessor> it =
wrappedProcessors.values().iterator();
while (it.hasNext()) {
- KeyValueHolder<NamedNode, InstrumentationProcessor<?>> holder
= it.next();
- RouteDefinition def =
ProcessorDefinitionHelper.getRoute(holder.getKey());
- if (def != null && id.equals(def.getId())) {
- managedProcessors.remove(holder.getKey());
+ WrappedProcessor holder = it.next();
+ if (id.equals(holder.route().getId())) {
+ if (isRouteDefinition(holder)) {
+ managedProcessors.remove(holder.definition());
+ } else {
+ shared.add(holder.definition());
+ }
it.remove();
}
}
+ // keep the managed object of a shared definition that is still in
use by another route
+ if (!shared.isEmpty()) {
+ for (WrappedProcessor holder : wrappedProcessors.values()) {
+ shared.remove(holder.definition());
+ }
+ shared.forEach(managedProcessors::remove);
+ }
}
}
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/InstrumentationInterceptStrategyTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/InstrumentationInterceptStrategyTest.java
new file mode 100644
index 000000000000..41aac4079b28
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/InstrumentationInterceptStrategyTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.HashMap;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class InstrumentationInterceptStrategyTest {
+
+ @Test
+ public void testRouteMustNotBeNull() {
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> new InstrumentationInterceptStrategy(new HashMap<>(),
new HashMap<>(), null));
+ assertEquals("route must be specified", e.getMessage());
+ }
+}
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveContextScopedOnExceptionTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveContextScopedOnExceptionTest.java
new file mode 100644
index 000000000000..4f19c41b5bbb
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveContextScopedOnExceptionTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.lang.reflect.Field;
+import java.util.Map;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_PROCESSOR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The outputs of a context scoped onException are the same definition in
every route, and share one processor mbean.
+ */
+@DisabledOnOs(OS.AIX)
+class ManagedRouteRemoveContextScopedOnExceptionTest extends
ManagementTestSupport {
+
+ @Test
+ void testRemoveRouteKeepsMBeanOfOtherRoute() throws Exception {
+ MBeanServer mbeanServer = getMBeanServer();
+ ObjectName on = getCamelObjectName(TYPE_PROCESSOR, "myError");
+
+ template.sendBody("direct:a", "A");
+ template.sendBody("direct:b", "B");
+ assertEquals(2L, mbeanServer.getAttribute(on, "ExchangesTotal"));
+ assertEquals("a", mbeanServer.getAttribute(on, "RouteId"));
+
+ context.getRouteController().stopRoute("a");
+ context.removeRoute("a");
+
+ assertTrue(mbeanServer.isRegistered(on), "The onException processor
mbean should still be registered for route b");
+ assertEquals("b", mbeanServer.getAttribute(on, "RouteId"));
+ assertEquals("Started", mbeanServer.getAttribute(on, "State"));
+ DefaultManagementAgent agent = (DefaultManagementAgent)
context.getManagementStrategy().getManagementAgent();
+ assertTrue(agent.getRouteProcessorMBeanNames("b", false).contains(on),
"Should be a processor of route b");
+ assertFalse(agent.getRouteProcessorMBeanNames("a",
false).contains(on), "Should not be a processor of route a");
+
+ template.sendBody("direct:b", "B");
+ assertEquals(3L, mbeanServer.getAttribute(on, "ExchangesTotal"));
+ }
+
+ @Test
+ void testRemoveOtherRoute() throws Exception {
+ MBeanServer mbeanServer = getMBeanServer();
+ ObjectName on = getCamelObjectName(TYPE_PROCESSOR, "myError");
+
+ template.sendBody("direct:a", "A");
+ template.sendBody("direct:b", "B");
+
+ context.getRouteController().stopRoute("b");
+ context.removeRoute("b");
+
+ assertTrue(mbeanServer.isRegistered(on), "The onException processor
mbean should still be registered for route a");
+ assertEquals("a", mbeanServer.getAttribute(on, "RouteId"));
+ assertEquals("Started", mbeanServer.getAttribute(on, "State"));
+
+ template.sendBody("direct:a", "A");
+ assertEquals(3L, mbeanServer.getAttribute(on, "ExchangesTotal"));
+ }
+
+ @Test
+ void testRemoveAllRoutes() throws Exception {
+ MBeanServer mbeanServer = getMBeanServer();
+ ObjectName on = getCamelObjectName(TYPE_PROCESSOR, "myError");
+
+ template.sendBody("direct:a", "A");
+ template.sendBody("direct:b", "B");
+
+ context.getRouteController().stopRoute("a");
+ context.removeRoute("a");
+ context.getRouteController().stopRoute("b");
+ context.removeRoute("b");
+
+ assertFalse(mbeanServer.isRegistered(on), "The onException processor
mbean should be unregistered");
+ assertEquals(0, wrappedProcessors().size(), "No wrapped processors
should be left after removing all routes");
+ }
+
+ private Map<?, ?> wrappedProcessors() throws Exception {
+ JmxManagementLifecycleStrategy strategy =
context.getLifecycleStrategies().stream()
+ .filter(JmxManagementLifecycleStrategy.class::isInstance)
+ .map(JmxManagementLifecycleStrategy.class::cast)
+ .findFirst().orElseThrow();
+ Field field =
JmxManagementLifecycleStrategy.class.getDeclaredField("wrappedProcessors");
+ field.setAccessible(true);
+ return (Map<?, ?>) field.get(strategy);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // context scoped
+
onException(Exception.class).handled(true).to("mock:error").id("myError");
+
+ from("direct:a").routeId("a").throwException(new
IllegalArgumentException("Forced a"));
+ from("direct:b").routeId("b").throwException(new
IllegalArgumentException("Forced b"));
+ }
+ };
+ }
+}
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveSharedProcessorTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveSharedProcessorTest.java
new file mode 100644
index 000000000000..7a8cac41873a
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveSharedProcessorTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.RouteConfigurationBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_PROCESSOR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The outputs of a context scoped onCompletion and of the onException of a
route configuration are also the same
+ * definition in every route that uses them, and share one processor mbean.
+ */
+@DisabledOnOs(OS.AIX)
+class ManagedRouteRemoveSharedProcessorTest extends ManagementTestSupport {
+
+ @Test
+ void testRemoveRouteKeepsMBeansOfOtherRoute() throws Exception {
+ MBeanServer mbeanServer = getMBeanServer();
+ ObjectName done = getCamelObjectName(TYPE_PROCESSOR, "myDone");
+ ObjectName error = getCamelObjectName(TYPE_PROCESSOR, "myError");
+
+ template.sendBody("direct:a", "A");
+ template.sendBody("direct:b", "B");
+ assertEquals(2L, mbeanServer.getAttribute(done, "ExchangesTotal"));
+ assertEquals(2L, mbeanServer.getAttribute(error, "ExchangesTotal"));
+
+ context.getRouteController().stopRoute("a");
+ context.removeRoute("a");
+
+ for (ObjectName on : new ObjectName[] { done, error }) {
+ assertTrue(mbeanServer.isRegistered(on), on + " should still be
registered for route b");
+ assertEquals("b", mbeanServer.getAttribute(on, "RouteId"));
+ }
+ template.sendBody("direct:b", "B");
+ assertEquals(3L, mbeanServer.getAttribute(done, "ExchangesTotal"));
+ assertEquals(3L, mbeanServer.getAttribute(error, "ExchangesTotal"));
+ }
+
+ @Override
+ protected RoutesBuilder[] createRouteBuilders() {
+ RoutesBuilder configuration = new RouteConfigurationBuilder() {
+ @Override
+ public void configuration() {
+
routeConfiguration("myConfig").onException(Exception.class).handled(true).to("mock:error").id("myError");
+ }
+ };
+ RoutesBuilder routes = new RouteBuilder() {
+ @Override
+ public void configure() {
+ // context scoped
+ onCompletion().to("mock:done").id("myDone");
+
+ from("direct:a").routeId("a").routeConfigurationId("myConfig")
+ .throwException(new IllegalArgumentException("Forced
a"));
+ from("direct:b").routeId("b").routeConfigurationId("myConfig")
+ .throwException(new IllegalArgumentException("Forced
b"));
+ }
+ };
+ return new RoutesBuilder[] { configuration, routes };
+ }
+}
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 2efde4c330fa..3164ab7f11d3 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
@@ -742,6 +742,10 @@ succeeded on its second redelivery reported 1 instead of
2, and a route whose ex
completed reported 0), and a processor or route that the exchange went through
after a redelivery could count that
redelivery as well.
+The constructor of
`org.apache.camel.management.InstrumentationInterceptStrategy` (used internally
by
+`JmxManagementLifecycleStrategy`) now takes a map of
`InstrumentationInterceptStrategy.WrappedProcessor` values and the
+`Route` whose processors it instruments, which must not be `null`.
+
==== mbeansLevel=ContextOnly
With `mbeansLevel=ContextOnly` the components are no longer registered as
MBeans (like the endpoints, thread pools