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