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