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
833        //skip processors according the ManagementMBeansLevel
834        if (!getManagementStrategy().getManagementAgent().getMBeansLevel().isProcessors()) {
835            return false;
836        }
837        // skip on exception
838        if (processor instanceof OnExceptionDefinition) {
839            return false;
840        }
841        // skip on completion
842        if (processor instanceof OnCompletionDefinition) {
843            return false;
844        }
845        // skip intercept
846        if (processor instanceof InterceptDefinition) {
847            return false;
848        }
849        // skip policy
850        if (processor instanceof PolicyDefinition) {
851            return false;
852        }
853
854        // only if custom id assigned
855        boolean only = getManagementStrategy().getManagementAgent().getOnlyRegisterProcessorWithCustomId() != null
856                && getManagementStrategy().getManagementAgent().getOnlyRegisterProcessorWithCustomId();
857        if (only) {
858            return processor.hasCustomIdAssigned();
859        }
860
861        // use customer filter
862        return getManagementStrategy().manageProcessor(processor);
863    }
864
865    private ManagementStrategy getManagementStrategy() {
866        ObjectHelper.notNull(camelContext, "CamelContext");
867        return camelContext.getManagementStrategy();
868    }
869
870    private ManagementObjectStrategy getManagementObjectStrategy() {
871        ObjectHelper.notNull(camelContext, "CamelContext");
872        return camelContext.getManagementStrategy().getManagementObjectStrategy();
873    }
874
875    /**
876     * Strategy for managing the object
877     *
878     * @param  me        the managed object
879     * @throws Exception is thrown if error registering the object for management
880     */
881    protected void manageObject(Object me) throws Exception {
882        getManagementStrategy().manageObject(me);
883        if (me instanceof TimerListener) {
884            TimerListener timer = (TimerListener) me;
885            loadTimer.addTimerListener(timer);
886        }
887    }
888
889    /**
890     * Un-manages the object.
891     *
892     * @param  me        the managed object
893     * @throws Exception is thrown if error unregistering the managed object
894     */
895    protected void unmanageObject(Object me) throws Exception {
896        if (me instanceof TimerListener) {
897            TimerListener timer = (TimerListener) me;
898            loadTimer.removeTimerListener(timer);
899        }
900        getManagementStrategy().unmanageObject(me);
901    }
902
903    /**
904     * Whether or not to register the mbean.
905     * <p/>
906     * The {@link ManagementAgent} has options which controls when to register. This allows us to only register mbeans
907     * accordingly. For example by default any dynamic endpoints is not registered. This avoids to register excessive
908     * mbeans, which most often is not desired.
909     *
910     * @param  service the object to register
911     * @param  route   an optional route the mbean is associated with, can be <tt>null</tt>
912     * @return         <tt>true</tt> to register, <tt>false</tt> to skip registering
913     */
914    protected boolean shouldRegister(Object service, Route route) {
915        // the agent hasn't been started
916        if (!initialized) {
917            return false;
918        }
919
920        LOG.trace("Checking whether to register {} from route: {}", service, route);
921
922        //skip route according the ManagementMBeansLevel
923        if (!getManagementStrategy().getManagementAgent().getMBeansLevel().isRoutes()) {
924            return false;
925        }
926
927        ManagementAgent agent = getManagementStrategy().getManagementAgent();
928        if (agent == null) {
929            // do not register if no agent
930            return false;
931        }
932
933        // always register if we are starting CamelContext
934        if (getCamelContext().getStatus().isStarting()
935                || getCamelContext().getStatus().isInitializing()) {
936            return true;
937        }
938
939        // always register if we are setting up routes
940        if (getCamelContext().adapt(ExtendedCamelContext.class).isSetupRoutes()) {
941            return true;
942        }
943
944        // register if always is enabled
945        if (agent.getRegisterAlways()) {
946            return true;
947        }
948
949        // is it a known route then always accept
950        if (route != null && knowRouteIds.contains(route.getId())) {
951            return true;
952        }
953
954        // only register if we are starting a new route, and current thread is in starting routes mode
955        if (agent.getRegisterNewRoutes()) {
956            // no specific route, then fallback to see if this thread is starting routes
957            // which is kept as state on the camel context
958            return getCamelContext().getRouteController().isStartingRoutes();
959        }
960
961        return false;
962    }
963
964    @Override
965    protected void doStart() throws Exception {
966        ObjectHelper.notNull(camelContext, "CamelContext");
967
968        // defer starting the timer manager until CamelContext has been fully started
969        camelContext.addStartupListener(loadTimerStartupListener);
970    }
971
972    private final class TimerListenerManagerStartupListener implements StartupListener {
973
974        @Override
975        public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception {
976            // we are disabled either if configured explicit, or if level is off
977            boolean load = camelContext.getManagementStrategy().getManagementAgent().getLoadStatisticsEnabled() != null
978                    && camelContext.getManagementStrategy().getManagementAgent().getLoadStatisticsEnabled();
979            boolean disabled = !load || camelContext.getManagementStrategy().getManagementAgent().getStatisticsLevel()
980                                        == ManagementStatisticsLevel.Off;
981
982            LOG.debug("Load performance statistics {}", disabled ? "disabled" : "enabled");
983            if (!disabled) {
984                // must use 1 sec interval as the load statistics is based on 1 sec calculations
985                loadTimer.setInterval(1000);
986                // we have to defer enlisting timer lister manager as a service until CamelContext has been started
987                getCamelContext().addService(loadTimer);
988            }
989        }
990    }
991
992    @Override
993    protected void doStop() throws Exception {
994        initialized = false;
995        knowRouteIds.clear();
996        preServices.clear();
997        wrappedProcessors.clear();
998        managedBacklogTracers.clear();
999        managedBacklogDebuggers.clear();
1000        managedThreadPools.clear();
1001    }
1002
1003    /**
1004     * Class which holds any pre registration details.
1005     *
1006     * @see JmxManagementLifecycleStrategy#enlistPreRegisteredServices()
1007     */
1008    static final class PreRegisterService {
1009
1010        private String name;
1011        private Component component;
1012        private Endpoint endpoint;
1013        private CamelContext camelContext;
1014        private Service service;
1015        private Route route;
1016        private java.util.function.Consumer<JmxManagementLifecycleStrategy> runnable;
1017
1018        public PreRegisterService() {
1019        }
1020
1021        public PreRegisterService(java.util.function.Consumer<JmxManagementLifecycleStrategy> runnable) {
1022            this.runnable = runnable;
1023        }
1024
1025        public void onComponentAdd(String name, Component component) {
1026            this.name = name;
1027            this.component = component;
1028        }
1029
1030        public void onEndpointAdd(Endpoint endpoint) {
1031            this.endpoint = endpoint;
1032        }
1033
1034        public void onServiceAdd(CamelContext camelContext, Service service, Route route) {
1035            this.camelContext = camelContext;
1036            this.service = service;
1037            this.route = route;
1038        }
1039
1040        public String getName() {
1041            return name;
1042        }
1043
1044        public Component getComponent() {
1045            return component;
1046        }
1047
1048        public Endpoint getEndpoint() {
1049            return endpoint;
1050        }
1051
1052        public CamelContext getCamelContext() {
1053            return camelContext;
1054        }
1055
1056        public Service getService() {
1057            return service;
1058        }
1059
1060        public Route getRoute() {
1061            return route;
1062        }
1063
1064        public java.util.function.Consumer<JmxManagementLifecycleStrategy> getRunnable() {
1065            return runnable;
1066        }
1067
1068    }
1069
1070}