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