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