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