001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.management;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import java.util.concurrent.ThreadPoolExecutor;
028
029import javax.management.JMException;
030import javax.management.MalformedObjectNameException;
031import javax.management.ObjectName;
032
033import org.apache.camel.CamelContext;
034import org.apache.camel.CamelContextAware;
035import org.apache.camel.Channel;
036import org.apache.camel.Component;
037import org.apache.camel.Consumer;
038import org.apache.camel.Endpoint;
039import org.apache.camel.ManagementStatisticsLevel;
040import org.apache.camel.NamedNode;
041import org.apache.camel.NonManagedService;
042import org.apache.camel.Processor;
043import org.apache.camel.Producer;
044import org.apache.camel.Route;
045import org.apache.camel.RuntimeCamelException;
046import org.apache.camel.Service;
047import org.apache.camel.StartupListener;
048import org.apache.camel.TimerListener;
049import org.apache.camel.VetoCamelContextStartException;
050import org.apache.camel.api.management.mbean.ManagedProcessorAware;
051import org.apache.camel.cluster.CamelClusterService;
052import org.apache.camel.health.HealthCheckRegistry;
053import org.apache.camel.impl.debugger.BacklogTracer;
054import org.apache.camel.impl.debugger.DefaultBacklogDebugger;
055import org.apache.camel.management.mbean.ManagedAsyncProcessorAwaitManager;
056import org.apache.camel.management.mbean.ManagedBacklogDebugger;
057import org.apache.camel.management.mbean.ManagedBacklogTracer;
058import org.apache.camel.management.mbean.ManagedBeanIntrospection;
059import org.apache.camel.management.mbean.ManagedCamelContext;
060import org.apache.camel.management.mbean.ManagedConsumerCache;
061import org.apache.camel.management.mbean.ManagedDumpRouteStrategy;
062import org.apache.camel.management.mbean.ManagedEndpoint;
063import org.apache.camel.management.mbean.ManagedEndpointRegistry;
064import org.apache.camel.management.mbean.ManagedEndpointServiceRegistry;
065import org.apache.camel.management.mbean.ManagedExchangeFactoryManager;
066import org.apache.camel.management.mbean.ManagedInflightRepository;
067import org.apache.camel.management.mbean.ManagedProducerCache;
068import org.apache.camel.management.mbean.ManagedRestRegistry;
069import org.apache.camel.management.mbean.ManagedRoute;
070import org.apache.camel.management.mbean.ManagedRouteGroup;
071import org.apache.camel.management.mbean.ManagedRuntimeEndpointRegistry;
072import org.apache.camel.management.mbean.ManagedService;
073import org.apache.camel.management.mbean.ManagedShutdownStrategy;
074import org.apache.camel.management.mbean.ManagedSimpleFunctionRepository;
075import org.apache.camel.management.mbean.ManagedStreamCachingStrategy;
076import org.apache.camel.management.mbean.ManagedTaskManagerRegistry;
077import org.apache.camel.management.mbean.ManagedThrottlingExceptionRoutePolicy;
078import org.apache.camel.management.mbean.ManagedThrottlingInflightRoutePolicy;
079import org.apache.camel.management.mbean.ManagedTracer;
080import org.apache.camel.management.mbean.ManagedTransformerRegistry;
081import org.apache.camel.management.mbean.ManagedTypeConverterRegistry;
082import org.apache.camel.management.mbean.ManagedValidatorRegistry;
083import org.apache.camel.management.mbean.ManagedVariableRepository;
084import org.apache.camel.model.InterceptDefinition;
085import org.apache.camel.model.OnCompletionDefinition;
086import org.apache.camel.model.OnExceptionDefinition;
087import org.apache.camel.model.PolicyDefinition;
088import org.apache.camel.model.ProcessorDefinition;
089import org.apache.camel.model.ProcessorDefinitionHelper;
090import org.apache.camel.model.RouteDefinition;
091import org.apache.camel.spi.AsyncProcessorAwaitManager;
092import org.apache.camel.spi.BeanIntrospection;
093import org.apache.camel.spi.BrowsableVariableRepository;
094import org.apache.camel.spi.ConsumerCache;
095import org.apache.camel.spi.DataFormat;
096import org.apache.camel.spi.DumpRoutesStrategy;
097import org.apache.camel.spi.EndpointRegistry;
098import org.apache.camel.spi.EndpointServiceRegistry;
099import org.apache.camel.spi.EventNotifier;
100import org.apache.camel.spi.ExchangeFactoryManager;
101import org.apache.camel.spi.InflightRepository;
102import org.apache.camel.spi.InternalProcessor;
103import org.apache.camel.spi.LifecycleStrategy;
104import org.apache.camel.spi.ManagementAgent;
105import org.apache.camel.spi.ManagementInterceptStrategy.InstrumentationProcessor;
106import org.apache.camel.spi.ManagementNameStrategy;
107import org.apache.camel.spi.ManagementObjectStrategy;
108import org.apache.camel.spi.ManagementStrategy;
109import org.apache.camel.spi.ProducerCache;
110import org.apache.camel.spi.RestRegistry;
111import org.apache.camel.spi.RuntimeEndpointRegistry;
112import org.apache.camel.spi.ShutdownStrategy;
113import org.apache.camel.spi.SimpleFunctionRegistry;
114import org.apache.camel.spi.StreamCachingStrategy;
115import org.apache.camel.spi.Tracer;
116import org.apache.camel.spi.TransformerRegistry;
117import org.apache.camel.spi.TypeConverterRegistry;
118import org.apache.camel.spi.UnitOfWork;
119import org.apache.camel.spi.ValidatorRegistry;
120import org.apache.camel.support.TimerListenerManager;
121import org.apache.camel.support.service.ServiceSupport;
122import org.apache.camel.support.task.TaskManagerRegistry;
123import org.apache.camel.throttling.ThrottlingExceptionRoutePolicy;
124import org.apache.camel.throttling.ThrottlingInflightRoutePolicy;
125import org.apache.camel.util.KeyValueHolder;
126import org.apache.camel.util.ObjectHelper;
127import org.slf4j.Logger;
128import org.slf4j.LoggerFactory;
129
130/**
131 * Default JMX managed lifecycle strategy that registered objects using the configured
132 * {@link org.apache.camel.spi.ManagementStrategy}.
133 *
134 * @see org.apache.camel.spi.ManagementStrategy
135 */
136public class JmxManagementLifecycleStrategy extends ServiceSupport implements LifecycleStrategy, CamelContextAware {
137
138    private static final Logger LOG = LoggerFactory.getLogger(JmxManagementLifecycleStrategy.class);
139
140    // the wrapped processors is for performance counters, which are in use for the created routes
141    // when a route is removed, we should remove the associated processors from this map
142    private final Map<Processor, KeyValueHolder<NamedNode, InstrumentationProcessor<?>>> wrappedProcessors = new HashMap<>();
143    private final List<java.util.function.Consumer<JmxManagementLifecycleStrategy>> preServices = new ArrayList<>();
144    private final TimerListenerManager loadTimer = new ManagedLoadTimer();
145    private final TimerListenerManagerStartupListener loadTimerStartupListener = new TimerListenerManagerStartupListener();
146    private volatile CamelContext camelContext;
147    private volatile ManagedCamelContext camelContextMBean;
148    private volatile boolean initialized;
149    private final Set<String> knowRouteIds = new HashSet<>();
150    private final Map<BacklogTracer, ManagedBacklogTracer> managedBacklogTracers = new HashMap<>();
151    private final Map<DefaultBacklogDebugger, ManagedBacklogDebugger> managedBacklogDebuggers = new HashMap<>();
152    private final Map<ThreadPoolExecutor, Object> managedThreadPools = new HashMap<>();
153
154    public JmxManagementLifecycleStrategy() {
155    }
156
157    public JmxManagementLifecycleStrategy(CamelContext camelContext) {
158        this.camelContext = camelContext;
159    }
160
161    // used for handing over pre-services between a provisional lifecycycle strategy
162    // and then later the actual strategy to be used when using XML
163    List<java.util.function.Consumer<JmxManagementLifecycleStrategy>> getPreServices() {
164        return preServices;
165    }
166
167    // used for handing over pre-services between a provisional lifecycycle strategy
168    // and then later the actual strategy to be used when using XML
169    void addPreService(java.util.function.Consumer<JmxManagementLifecycleStrategy> preService) {
170        preServices.add(preService);
171    }
172
173    @Override
174    public CamelContext getCamelContext() {
175        return camelContext;
176    }
177
178    @Override
179    public void setCamelContext(CamelContext camelContext) {
180        this.camelContext = camelContext;
181    }
182
183    @Override
184    public void onContextStarting(CamelContext context) throws VetoCamelContextStartException {
185        Object mc = getManagementObjectStrategy().getManagedObjectForCamelContext(context);
186
187        String name = context.getName();
188        String managementName = context.getManagementName();
189
190        if (managementName == null) {
191            managementName = context.getManagementNameStrategy().getName();
192        }
193
194        try {
195            boolean done = false;
196            while (!done) {
197                ObjectName on = getManagementStrategy().getManagementObjectNameStrategy()
198                        .getObjectNameForCamelContext(managementName, name);
199                boolean exists = getManagementStrategy().isManagedName(on);
200                if (!exists) {
201                    done = true;
202                } else {
203                    // okay there exists already a CamelContext with this name, we can try to fix it by finding a free name
204                    boolean fixed = false;
205                    // if we use the default name strategy we can find a free name to use
206                    String newName = findFreeName(context.getManagementNameStrategy(), name);
207                    if (newName != null) {
208                        // use this as the fixed name
209                        fixed = true;
210                        done = true;
211                        managementName = newName;
212                    }
213                    // we could not fix it so veto starting camel
214                    if (!fixed) {
215                        throw new VetoCamelContextStartException(
216                                "CamelContext (" + context.getName() + ") with ObjectName[" + on + "] is already registered."
217                                                                 + " Make sure to use unique names on CamelContext when using multiple CamelContexts in the same MBeanServer.",
218                                context);
219                    } else {
220                        LOG.warn("This CamelContext({}) will be registered using the name: {} due to clash with an "
221                                 + "existing name already registered in MBeanServer.",
222                                context.getName(), managementName);
223                    }
224                }
225            }
226        } catch (VetoCamelContextStartException e) {
227            // rethrow veto
228            throw e;
229        } catch (Exception e) {
230            // must rethrow to allow CamelContext fallback to non JMX agent to allow
231            // Camel to continue to run
232            throw RuntimeCamelException.wrapRuntimeCamelException(e);
233        }
234
235        // set the name we are going to use
236        context.setManagementName(managementName);
237
238        // yes we made it and are initialized
239        initialized = true;
240
241        try {
242            manageObject(mc);
243        } catch (Exception e) {
244            // must rethrow to allow CamelContext fallback to non JMX agent to allow
245            // Camel to continue to run
246            throw RuntimeCamelException.wrapRuntimeCamelException(e);
247        }
248
249        if (mc instanceof ManagedCamelContext managedCamelContext) {
250            camelContextMBean = managedCamelContext;
251        }
252
253        // register any pre-registered now that we are initialized
254        enlistPreRegisteredServices();
255
256        // register health check if detected
257        HealthCheckRegistry hcr = context.getCamelContextExtension().getContextPlugin(HealthCheckRegistry.class);
258        if (hcr != null) {
259            try {
260                Object me = getManagementObjectStrategy().getManagedObjectForCamelHealth(camelContext, hcr);
261                if (me == null) {
262                    // endpoint should not be managed
263                    return;
264                }
265                manageObject(me);
266            } catch (Exception e) {
267                LOG.warn("Could not register CamelHealth MBean. This exception will be ignored.", e);
268            }
269        }
270
271        try {
272            Object me = getManagementObjectStrategy().getManagedObjectForRouteController(camelContext,
273                    camelContext.getRouteController());
274            if (me == null) {
275                // endpoint should not be managed
276                return;
277            }
278            manageObject(me);
279        } catch (Exception e) {
280            LOG.warn("Could not register RouteController MBean. This exception will be ignored.", e);
281        }
282    }
283
284    private String findFreeName(ManagementNameStrategy strategy, String name) throws MalformedObjectNameException {
285        // we cannot find a free name for fixed named strategies
286        if (strategy.isFixedName()) {
287            return null;
288        }
289
290        // okay try to find a free name
291        boolean done = false;
292        String newName = null;
293        while (!done) {
294            // compute the next name
295            newName = strategy.getNextName();
296            ObjectName on
297                    = getManagementStrategy().getManagementObjectNameStrategy().getObjectNameForCamelContext(newName, name);
298            done = !getManagementStrategy().isManagedName(on);
299            if (LOG.isTraceEnabled()) {
300                LOG.trace("Using name: {} in ObjectName[{}] exists? {}", name, on, done);
301            }
302        }
303        return newName;
304    }
305
306    /**
307     * After {@link CamelContext} has been enlisted in JMX using
308     * {@link #onContextStarted(org.apache.camel.CamelContext)} then we can enlist any pre-registered services as well,
309     * as we had to wait for {@link CamelContext} to be enlisted first.
310     * <p/>
311     * A component/endpoint/service etc. can be pre-registered when using dependency injection and annotations such as
312     * {@link org.apache.camel.Produce}, {@link org.apache.camel.EndpointInject}. Therefore, we need to capture those
313     * registrations up front, and then afterward enlist in JMX when {@link CamelContext} is being started.
314     */
315    private void enlistPreRegisteredServices() {
316        if (preServices.isEmpty()) {
317            return;
318        }
319
320        LOG.debug("Registering {} pre registered services", preServices.size());
321        for (java.util.function.Consumer<JmxManagementLifecycleStrategy> pre : preServices) {
322            pre.accept(this);
323        }
324
325        // we are done so clear the list
326        preServices.clear();
327    }
328
329    @Override
330    public void onContextStopped(CamelContext context) {
331        // the agent hasn't been started
332        if (!initialized) {
333            return;
334        }
335
336        try {
337            Object mc = getManagementObjectStrategy().getManagedObjectForRouteController(context, context.getRouteController());
338            // the context could have been removed already
339            if (getManagementStrategy().isManaged(mc)) {
340                unmanageObject(mc);
341            }
342        } catch (Exception e) {
343            LOG.warn("Could not unregister RouteController MBean", e);
344        }
345
346        try {
347            Object mc = getManagementObjectStrategy().getManagedObjectForCamelContext(context);
348            // the context could have been removed already
349            if (getManagementStrategy().isManaged(mc)) {
350                unmanageObject(mc);
351            }
352        } catch (Exception e) {
353            LOG.warn("Could not unregister CamelContext MBean", e);
354        }
355
356        HealthCheckRegistry hcr = context.getCamelContextExtension().getContextPlugin(HealthCheckRegistry.class);
357        if (hcr != null) {
358            try {
359                Object mc = getManagementObjectStrategy().getManagedObjectForCamelHealth(context, hcr);
360                // the context could have been removed already
361                if (getManagementStrategy().isManaged(mc)) {
362                    unmanageObject(mc);
363                }
364            } catch (Exception e) {
365                LOG.warn("Could not unregister CamelHealth MBean", e);
366            }
367        }
368
369        camelContextMBean = null;
370    }
371
372    @Override
373    public void onComponentAdd(String name, Component component) {
374        // always register components as there are only a few of those
375        if (!initialized) {
376            // pre register so we can register later when we have been initialized
377            preServices.add(lf -> lf.onComponentAdd(name, component));
378            return;
379        }
380        try {
381            Object mc = getManagementObjectStrategy().getManagedObjectForComponent(camelContext, component, name);
382            manageObject(mc);
383        } catch (Exception e) {
384            LOG.warn("Could not register Component MBean", e);
385        }
386    }
387
388    @Override
389    public void onComponentRemove(String name, Component component) {
390        // the agent hasn't been started
391        if (!initialized) {
392            return;
393        }
394        try {
395            Object mc = getManagementObjectStrategy().getManagedObjectForComponent(camelContext, component, name);
396            unmanageObject(mc);
397        } catch (Exception e) {
398            LOG.warn("Could not unregister Component MBean", e);
399        }
400    }
401
402    /**
403     * If the endpoint is an instance of ManagedResource then register it with the mbean server, if it is not then wrap
404     * the endpoint in a {@link ManagedEndpoint} and register that with the mbean server.
405     *
406     * @param endpoint the Endpoint attempted to be added
407     */
408    @Override
409    public void onEndpointAdd(Endpoint endpoint) {
410        if (!initialized) {
411            // pre register so we can register later when we have been initialized
412            preServices.add(lf -> lf.onEndpointAdd(endpoint));
413            return;
414        }
415
416        if (!shouldRegister(endpoint, null)) {
417            // avoid registering if not needed
418            return;
419        }
420
421        try {
422            Object me = getManagementObjectStrategy().getManagedObjectForEndpoint(camelContext, endpoint);
423            if (me == null) {
424                // endpoint should not be managed
425                return;
426            }
427            manageObject(me);
428        } catch (Exception e) {
429            LOG.warn("Could not register Endpoint MBean for endpoint: {}. This exception will be ignored.", endpoint, e);
430        }
431    }
432
433    @Override
434    public void onEndpointRemove(Endpoint endpoint) {
435        // the agent hasn't been started
436        if (!initialized) {
437            return;
438        }
439
440        try {
441            Object me = getManagementObjectStrategy().getManagedObjectForEndpoint(camelContext, endpoint);
442            unmanageObject(me);
443        } catch (Exception e) {
444            LOG.warn("Could not unregister Endpoint MBean for endpoint: {}. This exception will be ignored.", endpoint, e);
445        }
446    }
447
448    @Override
449    public void onServiceAdd(CamelContext context, Service service, Route route) {
450        if (!initialized) {
451            // pre-register so we can register later when we have been initialized
452            preServices.add(lf -> lf.onServiceAdd(camelContext, service, route));
453            return;
454        }
455
456        // services can by any kind of misc type but also processors
457        // so we have special logic when its a processor
458
459        if (!shouldRegister(service, route)) {
460            // avoid registering if not needed
461            return;
462        }
463
464        Object managedObject = getManagedObjectForService(context, service, route);
465        if (managedObject == null) {
466            // service should not be managed
467            return;
468        }
469
470        // skip already managed services, for example if a route has been restarted
471        if (getManagementStrategy().isManaged(managedObject)) {
472            LOG.trace("The service is already managed: {}", service);
473            // re-attach processor on mben so we can reuse existing MBean but with newly created processor
474            if (managedObject instanceof ManagedProcessorAware mpa && service instanceof Processor processor) {
475                mpa.setProcessor(processor);
476            }
477            return;
478        }
479
480        try {
481            manageObject(managedObject);
482        } catch (Exception e) {
483            LOG.warn("Could not register service: {} as Service MBean.", service, e);
484        }
485    }
486
487    @Override
488    public void onServiceRemove(CamelContext context, Service service, Route route) {
489        onServiceRemove(context, service, route, true);
490    }
491
492    @Override
493    public void onServiceRemove(CamelContext context, Service service, Route route, boolean shutdown) {
494        // the agent hasn't been started
495        if (!initialized) {
496            return;
497        }
498        // always remove consumer or producer
499        boolean consumerOrProducer = service instanceof Consumer || service instanceof Producer;
500        if (!consumerOrProducer && !shutdown) {
501            // only remove service if being shutdown
502            return;
503        }
504
505        Object managedObject = getManagedObjectForService(context, service, route);
506        if (managedObject != null) {
507            try {
508                unmanageObject(managedObject);
509            } catch (Exception e) {
510                LOG.warn("Could not unregister service: {} as Service MBean.", service, e);
511            }
512        }
513    }
514
515    private Object getManagedObjectForService(CamelContext context, Service service, Route route) {
516        // skip channel, UoW and dont double wrap instrumentation
517        if (service instanceof Channel || service instanceof UnitOfWork || service instanceof InstrumentationProcessor) {
518            return null;
519        }
520
521        // skip non managed services
522        if (service instanceof NonManagedService) {
523            return null;
524        }
525
526        Object answer = null;
527
528        if (service instanceof BacklogTracer backlogTracer) {
529            // special for backlog tracer
530            ManagedBacklogTracer mt = managedBacklogTracers.get(backlogTracer);
531            if (mt == null) {
532                mt = new ManagedBacklogTracer(context, backlogTracer);
533                mt.init(getManagementStrategy());
534                managedBacklogTracers.put(backlogTracer, mt);
535            }
536            return mt;
537        } else if (service instanceof DefaultBacklogDebugger backlogDebugger) {
538            // special for backlog debugger
539            ManagedBacklogDebugger md = managedBacklogDebuggers.get(backlogDebugger);
540            if (md == null) {
541                md = new ManagedBacklogDebugger(context, backlogDebugger);
542                md.init(getManagementStrategy());
543                managedBacklogDebuggers.put(backlogDebugger, md);
544            }
545            return md;
546        } else if (service instanceof Tracer tracer) {
547            ManagedTracer mt = new ManagedTracer(camelContext, tracer);
548            mt.init(getManagementStrategy());
549            answer = mt;
550        } else if (service instanceof DumpRoutesStrategy dumpRoutesStrategy) {
551            ManagedDumpRouteStrategy mdrs = new ManagedDumpRouteStrategy(camelContext, dumpRoutesStrategy);
552            mdrs.init(getManagementStrategy());
553            answer = mdrs;
554        } else if (service instanceof DataFormat dataFormat) {
555            answer = getManagementObjectStrategy().getManagedObjectForDataFormat(context, dataFormat);
556        } else if (service instanceof Producer producer) {
557            answer = getManagementObjectStrategy().getManagedObjectForProducer(context, producer);
558        } else if (service instanceof Consumer consumer) {
559            answer = getManagementObjectStrategy().getManagedObjectForConsumer(context, consumer);
560        } else if (service instanceof Processor processor) {
561            // special for processors as we need to do some extra work
562            return getManagedObjectForProcessor(context, processor, route);
563        } else if (service instanceof ThrottlingInflightRoutePolicy throttlingInflightRoutePolicy) {
564            answer = new ManagedThrottlingInflightRoutePolicy(context, throttlingInflightRoutePolicy);
565        } else if (service instanceof ThrottlingExceptionRoutePolicy throttlingExceptionRoutePolicy) {
566            answer = new ManagedThrottlingExceptionRoutePolicy(context, throttlingExceptionRoutePolicy);
567        } else if (service instanceof ConsumerCache consumerCache) {
568            answer = new ManagedConsumerCache(context, consumerCache);
569        } else if (service instanceof ProducerCache producerCache) {
570            answer = new ManagedProducerCache(context, producerCache);
571        } else if (service instanceof ExchangeFactoryManager exchangeFactoryManager) {
572            answer = new ManagedExchangeFactoryManager(context, exchangeFactoryManager);
573        } else if (service instanceof EndpointRegistry endpointRegistry) {
574            answer = new ManagedEndpointRegistry(context, endpointRegistry);
575        } else if (service instanceof BeanIntrospection beanIntrospection) {
576            answer = new ManagedBeanIntrospection(context, beanIntrospection);
577        } else if (service instanceof TypeConverterRegistry typeConverterRegistry) {
578            answer = new ManagedTypeConverterRegistry(context, typeConverterRegistry);
579        } else if (service instanceof RestRegistry restRegistry) {
580            answer = new ManagedRestRegistry(context, restRegistry);
581        } else if (service instanceof EndpointServiceRegistry endpointServiceRegistry) {
582            answer = new ManagedEndpointServiceRegistry(context, endpointServiceRegistry);
583        } else if (service instanceof InflightRepository inflightRepository) {
584            answer = new ManagedInflightRepository(context, inflightRepository);
585        } else if (service instanceof AsyncProcessorAwaitManager asyncProcessorAwaitManager) {
586            answer = new ManagedAsyncProcessorAwaitManager(context, asyncProcessorAwaitManager);
587        } else if (service instanceof RuntimeEndpointRegistry runtimeEndpointRegistry) {
588            answer = new ManagedRuntimeEndpointRegistry(context, runtimeEndpointRegistry);
589        } else if (service instanceof StreamCachingStrategy streamCachingStrategy) {
590            answer = new ManagedStreamCachingStrategy(context, streamCachingStrategy);
591        } else if (service instanceof ShutdownStrategy shutdownStrategy) {
592            answer = new ManagedShutdownStrategy(context, shutdownStrategy);
593        } else if (service instanceof EventNotifier eventNotifier)
594            answer = getManagementObjectStrategy().getManagedObjectForEventNotifier(context, eventNotifier);
595        else if (service instanceof TransformerRegistry transformerRegistry) {
596            answer = new ManagedTransformerRegistry(context, transformerRegistry);
597        } else if (service instanceof ValidatorRegistry validatorRegistry) {
598            answer = new ManagedValidatorRegistry(context, validatorRegistry);
599        } else if (service instanceof BrowsableVariableRepository variableRepository) {
600            answer = new ManagedVariableRepository(context, variableRepository);
601        } else if (service instanceof TaskManagerRegistry registry) {
602            answer = new ManagedTaskManagerRegistry(camelContext, registry);
603        } else if (service instanceof CamelClusterService camelClusterService) {
604            answer = getManagementObjectStrategy().getManagedObjectForClusterService(context, camelClusterService);
605        } else if (service instanceof SimpleFunctionRegistry registry) {
606            answer = new ManagedSimpleFunctionRepository(context, registry);
607        } else if (service != null) {
608            // fallback as generic service
609            answer = getManagementObjectStrategy().getManagedObjectForService(context, service);
610        }
611
612        if (answer instanceof ManagedService ms) {
613            ms.setRoute(route);
614            ms.init(getManagementStrategy());
615        }
616
617        return answer;
618    }
619
620    private Object getManagedObjectForProcessor(CamelContext context, Processor processor, Route route) {
621        // a bit of magic here as the processors we want to manage have already been registered
622        // in the wrapped processors map when Camel have instrumented the route on route initialization
623        // so the idea is now to only manage the processors from the map
624        KeyValueHolder<NamedNode, InstrumentationProcessor<?>> holder = wrappedProcessors.get(processor);
625        if (holder == null) {
626            // skip as it's not a well known processor we want to manage anyway, such as Channel/UnitOfWork/Pipeline etc.
627            return null;
628        }
629
630        // get the managed object as it can be a specialized type such as a Delayer/Throttler etc.
631        Object managedObject
632                = getManagementObjectStrategy().getManagedObjectForProcessor(context, processor, holder.getKey(), route);
633        // only manage if we have a name for it as otherwise we do not want to manage it anyway
634        if (managedObject != null) {
635            // is it a performance counter then we need to set our counter
636            if (managedObject instanceof PerformanceCounter) {
637                InstrumentationProcessor<?> counter = holder.getValue();
638                if (counter != null) {
639                    // change counter to us
640                    counter.setCounter(managedObject);
641                }
642            }
643        }
644
645        return managedObject;
646    }
647
648    @Override
649    public void onRoutesAdd(Collection<Route> routes) {
650        for (Route route : routes) {
651
652            // if we are starting CamelContext or either of the two options has been
653            // enabled, then enlist the route as a known route
654            if (getCamelContext().getStatus().isStarting()
655                    || getManagementStrategy().getManagementAgent().getRegisterAlways()
656                    || getManagementStrategy().getManagementAgent().getRegisterNewRoutes()) {
657                // register as known route id
658                knowRouteIds.add(route.getId());
659            }
660
661            if (!shouldRegister(route, route)) {
662                // avoid registering if not needed, skip to next route
663                continue;
664            }
665
666            Object mr = getManagementObjectStrategy().getManagedObjectForRoute(camelContext, route);
667
668            // skip already managed routes, for example if the route has been restarted
669            if (getManagementStrategy().isManaged(mr)) {
670                LOG.trace("The route is already managed: {}", route);
671                continue;
672            }
673            ManagedRouteGroup mrg = (ManagedRouteGroup) getManagementObjectStrategy()
674                    .getManagedObjectForRouteGroup(camelContext, route.getGroup());
675
676            // get the wrapped instrumentation processor from this route
677            // and set me as the counter
678            Processor processor = route.getProcessor();
679            if (processor instanceof InternalProcessor internal && mr instanceof ManagedRoute routeMBean) {
680                DefaultInstrumentationProcessor task = internal.getAdvice(DefaultInstrumentationProcessor.class);
681                if (task != null) {
682                    // we need to wrap the counter with the camel context, so we get stats updated on the context as well
683                    if (camelContextMBean != null) {
684                        CompositePerformanceCounter wrapper
685                                = new CompositePerformanceCounter(routeMBean, camelContextMBean, mrg);
686                        task.setCounter(wrapper);
687                    } else {
688                        task.setCounter(routeMBean);
689                    }
690                }
691            }
692
693            try {
694                manageObject(mr);
695            } catch (JMException e) {
696                LOG.warn("Could not register Route MBean", e);
697            } catch (Exception e) {
698                LOG.warn("Could not create Route MBean", e);
699            }
700
701            // also manage the route group
702            if (mrg != null && !getManagementStrategy().isManaged(mrg)) {
703                try {
704                    manageObject(mrg);
705                } catch (JMException e) {
706                    LOG.warn("Could not register RouteGroup MBean", e);
707                } catch (Exception e) {
708                    LOG.warn("Could not create RouteGroup MBean", e);
709                }
710            }
711        }
712    }
713
714    @Override
715    public void onRoutesRemove(Collection<Route> routes) {
716        // the agent hasn't been started
717        if (!initialized) {
718            return;
719        }
720
721        for (Route route : routes) {
722            Object mr = getManagementObjectStrategy().getManagedObjectForRoute(camelContext, route);
723
724            // skip unmanaged routes
725            if (!getManagementStrategy().isManaged(mr)) {
726                LOG.trace("The route is not managed: {}", route);
727                continue;
728            }
729
730            try {
731                unmanageObject(mr);
732            } catch (Exception e) {
733                LOG.warn("Could not unregister Route MBean", e);
734            }
735
736            // remove from known routes ids, as the route has been removed
737            knowRouteIds.remove(route.getId());
738
739            // if there are no routes anymore with a given route group then the mbean should be removed
740            if (route.getGroup() != null) {
741                int size = camelContext.getRoutesByGroup(route.getGroup()).size();
742                // if size is 1 then it is because its ourselves that we are currently removing
743                if (size <= 1) {
744                    ManagedRouteGroup mrg = (ManagedRouteGroup) getManagementObjectStrategy()
745                            .getManagedObjectForRouteGroup(camelContext, route.getGroup());
746                    try {
747                        unmanageObject(mrg);
748                    } catch (Exception e) {
749                        LOG.warn("Could not unregister Route Group MBean", e);
750                    }
751                }
752            }
753        }
754
755        // after the routes has been removed, we should clear the wrapped processors as we no longer need them
756        // as they were just a provisional map used during creation of routes
757        removeWrappedProcessorsForRoutes(routes);
758    }
759
760    @Override
761    public void onThreadPoolAdd(
762            CamelContext camelContext, ThreadPoolExecutor threadPool, String id,
763            String sourceId, String routeId, String threadPoolProfileId) {
764
765        if (!initialized) {
766            // pre register so we can register later when we have been initialized
767            preServices.add(lf -> lf.onThreadPoolAdd(camelContext, threadPool, id, sourceId, routeId, threadPoolProfileId));
768            return;
769        }
770
771        if (!shouldRegister(threadPool, null)) {
772            // avoid registering if not needed
773            return;
774        }
775
776        Object mtp = getManagementObjectStrategy().getManagedObjectForThreadPool(camelContext, threadPool, id, sourceId,
777                routeId, threadPoolProfileId);
778
779        // skip already managed services, for example if a route has been restarted
780        if (getManagementStrategy().isManaged(mtp)) {
781            LOG.trace("The thread pool is already managed: {}", threadPool);
782            return;
783        }
784
785        try {
786            manageObject(mtp);
787            // store a reference so we can unmanage from JMX when the thread pool is removed
788            // we need to keep track here, as we cannot re-construct the thread pool ObjectName when removing the thread pool
789            managedThreadPools.put(threadPool, mtp);
790        } catch (Exception e) {
791            LOG.warn("Could not register thread pool: {} as ThreadPool MBean.", threadPool, e);
792        }
793    }
794
795    @Override
796    public void onThreadPoolRemove(CamelContext camelContext, ThreadPoolExecutor threadPool) {
797        if (!initialized) {
798            return;
799        }
800
801        // lookup the thread pool and remove it from JMX
802        Object mtp = managedThreadPools.remove(threadPool);
803        if (mtp != null) {
804            // skip unmanaged routes
805            if (!getManagementStrategy().isManaged(mtp)) {
806                LOG.trace("The thread pool is not managed: {}", threadPool);
807                return;
808            }
809
810            try {
811                unmanageObject(mtp);
812            } catch (Exception e) {
813                LOG.warn("Could not unregister ThreadPool MBean", e);
814            }
815        }
816    }
817
818    @Override
819    public void onRouteContextCreate(Route route) {
820        // Create a map (ProcessorType -> PerformanceCounter)
821        // to be passed to InstrumentationInterceptStrategy.
822        Map<NamedNode, PerformanceCounter> registeredCounters = new HashMap<>();
823
824        // Each processor in a route will have its own performance counter.
825        // These performance counter will be embedded to InstrumentationProcessor
826        // and wrap the appropriate processor by InstrumentationInterceptStrategy.
827        RouteDefinition routeDefinition = (RouteDefinition) route.getRoute();
828
829        // register performance counters for all processors and its children
830        for (ProcessorDefinition<?> processor : routeDefinition.getOutputs()) {
831            registerPerformanceCounters(route, processor, registeredCounters);
832        }
833
834        // set this managed intercept strategy that executes the JMX instrumentation for performance metrics
835        // so our registered counters can be used for fine-grained performance instrumentation
836        route.setManagementInterceptStrategy(new InstrumentationInterceptStrategy(registeredCounters, wrappedProcessors));
837    }
838
839    /**
840     * Removes the wrapped processors for the given routes, as they are no longer in use.
841     * <p/>
842     * This is needed to avoid accumulating memory, if a lot of routes is being added and removed.
843     *
844     * @param routes the routes
845     */
846    private void removeWrappedProcessorsForRoutes(Collection<Route> routes) {
847        // loop the routes, and remove the route associated wrapped processors, as they are no longer in use
848        for (Route route : routes) {
849            String id = route.getId();
850
851            Iterator<KeyValueHolder<NamedNode, InstrumentationProcessor<?>>> it = wrappedProcessors.values().iterator();
852            while (it.hasNext()) {
853                KeyValueHolder<NamedNode, InstrumentationProcessor<?>> holder = it.next();
854                RouteDefinition def = ProcessorDefinitionHelper.getRoute(holder.getKey());
855                if (def != null && id.equals(def.getId())) {
856                    it.remove();
857                }
858            }
859        }
860    }
861
862    private void registerPerformanceCounters(
863            Route route, ProcessorDefinition<?> processor,
864            Map<NamedNode, PerformanceCounter> registeredCounters) {
865
866        // traverse children if any exists
867        List<ProcessorDefinition<?>> children = processor.getOutputs();
868        for (ProcessorDefinition<?> child : children) {
869            registerPerformanceCounters(route, child, registeredCounters);
870        }
871
872        // skip processors that should not be registered
873        if (!registerProcessor(processor)) {
874            return;
875        }
876
877        // okay this is a processor we would like to manage so create the
878        // a delegate performance counter that acts as the placeholder in the interceptor
879        // that then delegates to the real mbean which we register later in the onServiceAdd method
880        DelegatePerformanceCounter pc = new DelegatePerformanceCounter();
881        // set statistics enabled depending on the option
882        boolean enabled = camelContext.getManagementStrategy().getManagementAgent().getStatisticsLevel().isDefaultOrExtended();
883        pc.setStatisticsEnabled(enabled);
884
885        // and add it as a a registered counter that will be used lazy when Camel
886        // does the instrumentation of the route and adds the InstrumentationProcessor
887        // that does the actual performance metrics gatherings at runtime
888        registeredCounters.put(processor, pc);
889    }
890
891    /**
892     * Should the given processor be registered.
893     */
894    protected boolean registerProcessor(ProcessorDefinition<?> processor) {
895
896        //skip processors according the ManagementMBeansLevel
897        if (!getManagementStrategy().getManagementAgent().getMBeansLevel().isProcessors()) {
898            return false;
899        }
900        // skip on exception
901        if (processor instanceof OnExceptionDefinition) {
902            return false;
903        }
904        // skip on completion
905        if (processor instanceof OnCompletionDefinition) {
906            return false;
907        }
908        // skip intercept
909        if (processor instanceof InterceptDefinition) {
910            return false;
911        }
912        // skip policy
913        if (processor instanceof PolicyDefinition) {
914            return false;
915        }
916
917        // only if custom id assigned
918        boolean only = getManagementStrategy().getManagementAgent().getOnlyRegisterProcessorWithCustomId() != null
919                && getManagementStrategy().getManagementAgent().getOnlyRegisterProcessorWithCustomId();
920        if (only) {
921            return processor.hasCustomIdAssigned();
922        }
923
924        // use customer filter
925        return getManagementStrategy().manageProcessor(processor);
926    }
927
928    private ManagementStrategy getManagementStrategy() {
929        ObjectHelper.notNull(camelContext, "CamelContext");
930        return camelContext.getManagementStrategy();
931    }
932
933    private ManagementObjectStrategy getManagementObjectStrategy() {
934        ObjectHelper.notNull(camelContext, "CamelContext");
935        return camelContext.getManagementStrategy().getManagementObjectStrategy();
936    }
937
938    /**
939     * Strategy for managing the object
940     *
941     * @param  me        the managed object
942     * @throws Exception is thrown if error registering the object for management
943     */
944    protected void manageObject(Object me) throws Exception {
945        getManagementStrategy().manageObject(me);
946        if (me instanceof TimerListener timer) {
947            loadTimer.addTimerListener(timer);
948        }
949    }
950
951    /**
952     * Un-manages the object.
953     *
954     * @param  me        the managed object
955     * @throws Exception is thrown if error unregistering the managed object
956     */
957    protected void unmanageObject(Object me) throws Exception {
958        if (me instanceof TimerListener timer) {
959            loadTimer.removeTimerListener(timer);
960        }
961        getManagementStrategy().unmanageObject(me);
962    }
963
964    /**
965     * Whether to register the mbean.
966     * <p/>
967     * The {@link ManagementAgent} has options which controls when to register. This allows us to only register mbeans
968     * accordingly. For example by default any dynamic endpoints is not registered. This avoids to register excessive
969     * mbeans, which most often is not desired.
970     *
971     * @param  service the object to register
972     * @param  route   an optional route the mbean is associated with, can be <tt>null</tt>
973     * @return         <tt>true</tt> to register, <tt>false</tt> to skip registering
974     */
975    protected boolean shouldRegister(Object service, Route route) {
976        // the agent hasn't been started
977        if (!initialized) {
978            return false;
979        }
980
981        LOG.trace("Checking whether to register {} from route: {}", service, route);
982
983        //skip route according the ManagementMBeansLevel
984        if (!getManagementStrategy().getManagementAgent().getMBeansLevel().isRoutes()) {
985            return false;
986        }
987
988        ManagementAgent agent = getManagementStrategy().getManagementAgent();
989        if (agent == null) {
990            // do not register if no agent
991            return false;
992        }
993
994        if (route != null && route.isCreatedByKamelet() && !agent.getRegisterRoutesCreateByKamelet()) {
995            // skip routes created from kamelets
996            return false;
997        }
998        if (route != null && route.isCreatedByRouteTemplate() && !agent.getRegisterRoutesCreateByTemplate()) {
999            // skip routes created from route templates
1000            return false;
1001        }
1002
1003        // always register if we are starting CamelContext
1004        if (getCamelContext().getStatus().isStarting()
1005                || getCamelContext().getStatus().isInitializing()) {
1006            return true;
1007        }
1008
1009        // always register if we are setting up routes
1010        if (getCamelContext().getCamelContextExtension().isSetupRoutes()) {
1011            return true;
1012        }
1013
1014        // register if always is enabled
1015        if (agent.getRegisterAlways()) {
1016            return true;
1017        }
1018
1019        // is it a known route then always accept
1020        if (route != null && knowRouteIds.contains(route.getId())) {
1021            return true;
1022        }
1023
1024        // only register if we are starting a new route, and current thread is in starting routes mode
1025        if (agent.getRegisterNewRoutes()) {
1026            // no specific route, then fallback to see if this thread is starting routes
1027            // which is kept as state on the camel context
1028            return getCamelContext().getRouteController().isStartingRoutes();
1029        }
1030
1031        return false;
1032    }
1033
1034    @Override
1035    protected void doStart() throws Exception {
1036        ObjectHelper.notNull(camelContext, "CamelContext");
1037
1038        // defer starting the timer manager until CamelContext has been fully started
1039        camelContext.addStartupListener(loadTimerStartupListener);
1040    }
1041
1042    private final class TimerListenerManagerStartupListener implements StartupListener {
1043
1044        @Override
1045        public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception {
1046            // we are disabled either if configured explicit, or if level is off
1047            boolean load = camelContext.getManagementStrategy().getManagementAgent().getLoadStatisticsEnabled() != null
1048                    && camelContext.getManagementStrategy().getManagementAgent().getLoadStatisticsEnabled();
1049            boolean disabled = !load || camelContext.getManagementStrategy().getManagementAgent().getStatisticsLevel()
1050                                        == ManagementStatisticsLevel.Off;
1051
1052            LOG.debug("Load performance statistics {}", disabled ? "disabled" : "enabled");
1053            if (!disabled) {
1054                // must use 1 sec interval as the load statistics is based on 1 sec calculations
1055                loadTimer.setInterval(1000);
1056                // we have to defer enlisting timer lister manager as a service until CamelContext has been started
1057                getCamelContext().addService(loadTimer);
1058            }
1059        }
1060    }
1061
1062    @Override
1063    protected void doStop() throws Exception {
1064        initialized = false;
1065        knowRouteIds.clear();
1066        preServices.clear();
1067        wrappedProcessors.clear();
1068        managedBacklogTracers.clear();
1069        managedBacklogDebuggers.clear();
1070        managedThreadPools.clear();
1071    }
1072
1073}