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