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.mbean;
018
019import java.io.ByteArrayInputStream;
020import java.io.InputStream;
021import java.net.URLDecoder;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Comparator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.TimeUnit;
029
030import javax.management.MBeanServer;
031import javax.management.ObjectName;
032
033import org.w3c.dom.Document;
034
035import org.apache.camel.CamelContext;
036import org.apache.camel.CatalogCamelContext;
037import org.apache.camel.Endpoint;
038import org.apache.camel.ExtendedCamelContext;
039import org.apache.camel.ManagementStatisticsLevel;
040import org.apache.camel.Producer;
041import org.apache.camel.ProducerTemplate;
042import org.apache.camel.Route;
043import org.apache.camel.TimerListener;
044import org.apache.camel.api.management.ManagedResource;
045import org.apache.camel.api.management.mbean.ManagedCamelContextMBean;
046import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
047import org.apache.camel.api.management.mbean.ManagedRouteMBean;
048import org.apache.camel.api.management.mbean.ManagedStepMBean;
049import org.apache.camel.model.Model;
050import org.apache.camel.model.RouteDefinition;
051import org.apache.camel.model.RoutesDefinition;
052import org.apache.camel.model.rest.RestDefinition;
053import org.apache.camel.model.rest.RestsDefinition;
054import org.apache.camel.spi.ManagementStrategy;
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058@ManagedResource(description = "Managed CamelContext")
059public class ManagedCamelContext extends ManagedPerformanceCounter implements TimerListener, ManagedCamelContextMBean {
060
061    private static final Logger LOG = LoggerFactory.getLogger(ManagedCamelContext.class);
062
063    private final CamelContext context;
064    private final LoadTriplet load = new LoadTriplet();
065    private final String jmxDomain;
066
067    public ManagedCamelContext(CamelContext context) {
068        this.context = context;
069        this.jmxDomain = context.getManagementStrategy().getManagementAgent().getMBeanObjectDomainName();
070    }
071
072    @Override
073    public void init(ManagementStrategy strategy) {
074        super.init(strategy);
075        boolean enabled = context.getManagementStrategy().getManagementAgent() != null && context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != ManagementStatisticsLevel.Off;
076        setStatisticsEnabled(enabled);
077    }
078
079    public CamelContext getContext() {
080        return context;
081    }
082
083    @Override
084    public String getCamelId() {
085        return context.getName();
086    }
087
088    @Override
089    public String getManagementName() {
090        return context.getManagementName();
091    }
092
093    @Override
094    public String getCamelVersion() {
095        return context.getVersion();
096    }
097
098    @Override
099    public String getState() {
100        return context.getStatus().name();
101    }
102
103    @Override
104    public String getUptime() {
105        return context.getUptime();
106    }
107
108    @Override
109    public long getUptimeMillis() {
110        return context.getUptimeMillis();
111    }
112
113    @Override
114    public String getManagementStatisticsLevel() {
115        if (context.getManagementStrategy().getManagementAgent() != null) {
116            return context.getManagementStrategy().getManagementAgent().getStatisticsLevel().name();
117        } else {
118            return null;
119        }
120    }
121
122    @Override
123    public String getClassResolver() {
124        return context.getClassResolver().getClass().getName();
125    }
126
127    @Override
128    public String getPackageScanClassResolver() {
129        return context.adapt(ExtendedCamelContext.class).getPackageScanClassResolver().getClass().getName();
130    }
131
132    @Override
133    public String getApplicationContextClassName() {
134        if (context.getApplicationContextClassLoader() != null) {
135            return context.getApplicationContextClassLoader().getClass().getName();
136        } else {
137            return null;
138        }
139    }
140
141    @Override
142    public String getHeadersMapFactoryClassName() {
143        return context.adapt(ExtendedCamelContext.class).getHeadersMapFactory().getClass().getName();
144    }
145
146    @Override
147    public Map<String, String> getGlobalOptions() {
148        if (context.getGlobalOptions().isEmpty()) {
149            return null;
150        }
151        return context.getGlobalOptions();
152    }
153
154    @Override
155    public String getGlobalOption(String key) throws Exception {
156        return context.getGlobalOption(key);
157    }
158
159    @Override
160    public void setGlobalOption(String key, String value) throws Exception {
161        context.getGlobalOptions().put(key, value);
162    }
163
164    @Override
165    public Boolean getTracing() {
166        return context.isTracing();
167    }
168
169    @Override
170    public void setTracing(Boolean tracing) {
171        context.setTracing(tracing);
172    }
173
174    public Integer getInflightExchanges() {
175        return (int) super.getExchangesInflight();
176    }
177
178    @Override
179    public Integer getTotalRoutes() {
180        return context.getRoutesSize();
181    }
182
183    @Override
184    public Integer getStartedRoutes() {
185        int started = 0;
186        for (Route route : context.getRoutes()) {
187            if (context.getRouteController().getRouteStatus(route.getId()).isStarted()) {
188                started++;
189            }
190        }
191        return started;
192    }
193
194    @Override
195    public void setTimeout(long timeout) {
196        context.getShutdownStrategy().setTimeout(timeout);
197    }
198
199    @Override
200    public long getTimeout() {
201        return context.getShutdownStrategy().getTimeout();
202    }
203
204    @Override
205    public void setTimeUnit(TimeUnit timeUnit) {
206        context.getShutdownStrategy().setTimeUnit(timeUnit);
207    }
208
209    @Override
210    public TimeUnit getTimeUnit() {
211        return context.getShutdownStrategy().getTimeUnit();
212    }
213
214    @Override
215    public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) {
216        context.getShutdownStrategy().setShutdownNowOnTimeout(shutdownNowOnTimeout);
217    }
218
219    @Override
220    public boolean isShutdownNowOnTimeout() {
221        return context.getShutdownStrategy().isShutdownNowOnTimeout();
222    }
223
224    @Override
225    public String getLoad01() {
226        double load1 = load.getLoad1();
227        if (Double.isNaN(load1)) {
228            // empty string if load statistics is disabled
229            return "";
230        } else {
231            return String.format("%.2f", load1);
232        }
233    }
234
235    @Override
236    public String getLoad05() {
237        double load5 = load.getLoad5();
238        if (Double.isNaN(load5)) {
239            // empty string if load statistics is disabled
240            return "";
241        } else {
242            return String.format("%.2f", load5);
243        }
244    }
245
246    @Override
247    public String getLoad15() {
248        double load15 = load.getLoad15();
249        if (Double.isNaN(load15)) {
250            // empty string if load statistics is disabled
251            return "";
252        } else {
253            return String.format("%.2f", load15);
254        }
255    }
256
257    @Override
258    public boolean isUseBreadcrumb() {
259        return context.isUseBreadcrumb();
260    }
261
262    @Override
263    public boolean isAllowUseOriginalMessage() {
264        return context.isAllowUseOriginalMessage();
265    }
266
267    @Override
268    public boolean isMessageHistory() {
269        return context.isMessageHistory() != null ? context.isMessageHistory() : false;
270    }
271
272    @Override
273    public boolean isLogMask() {
274        return context.isLogMask() != null ? context.isLogMask() : false;
275    }
276
277    @Override
278    public boolean isUseMDCLogging() {
279        return context.isUseMDCLogging();
280    }
281
282    @Override
283    public boolean isUseDataType() {
284        return context.isUseDataType();
285    }
286
287    @Override
288    public void onTimer() {
289        load.update(getInflightExchanges());
290    }
291
292    @Override
293    public void start() throws Exception {
294        if (context.isSuspended()) {
295            context.resume();
296        } else {
297            context.start();
298        }
299    }
300
301    @Override
302    public void stop() throws Exception {
303        context.stop();
304    }
305
306    @Override
307    public void restart() throws Exception {
308        context.stop();
309        context.start();
310    }
311
312    @Override
313    public void suspend() throws Exception {
314        context.suspend();
315    }
316
317    @Override
318    public void resume() throws Exception {
319        if (context.isSuspended()) {
320            context.resume();
321        } else {
322            throw new IllegalStateException("CamelContext is not suspended");
323        }
324    }
325
326    @Override
327    public void startAllRoutes() throws Exception {
328        context.getRouteController().startAllRoutes();
329    }
330
331    @Override
332    public boolean canSendToEndpoint(String endpointUri) {
333        try {
334            Endpoint endpoint = context.getEndpoint(endpointUri);
335            if (endpoint != null) {
336                Producer producer = endpoint.createProducer();
337                return producer != null;
338            }
339        } catch (Exception e) {
340            // ignore
341        }
342
343        return false;
344    }
345
346    @Override
347    public void sendBody(String endpointUri, Object body) throws Exception {
348        ProducerTemplate template = context.createProducerTemplate();
349        try {
350            template.sendBody(endpointUri, body);
351        } finally {
352            template.stop();
353        }
354    }
355
356    @Override
357    public void sendStringBody(String endpointUri, String body) throws Exception {
358        sendBody(endpointUri, body);
359    }
360
361    @Override
362    public void sendBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers) throws Exception {
363        ProducerTemplate template = context.createProducerTemplate();
364        try {
365            template.sendBodyAndHeaders(endpointUri, body, headers);
366        } finally {
367            template.stop();
368        }
369    }
370
371    @Override
372    public Object requestBody(String endpointUri, Object body) throws Exception {
373        ProducerTemplate template = context.createProducerTemplate();
374        Object answer = null;
375        try {
376            answer = template.requestBody(endpointUri, body);
377        } finally {
378            template.stop();
379        }
380        return answer;
381    }
382
383    @Override
384    public Object requestStringBody(String endpointUri, String body) throws Exception {
385        return requestBody(endpointUri, body);
386    }
387
388    @Override
389    public Object requestBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers) throws Exception {
390        ProducerTemplate template = context.createProducerTemplate();
391        Object answer = null;
392        try {
393            answer = template.requestBodyAndHeaders(endpointUri, body, headers);
394        } finally {
395            template.stop();
396        }
397        return answer;
398    }
399
400    @Override
401    public String dumpRestsAsXml() throws Exception {
402        return dumpRestsAsXml(false);
403    }
404
405    @Override
406    public String dumpRestsAsXml(boolean resolvePlaceholders) throws Exception {
407        List<RestDefinition> rests = context.getExtension(Model.class).getRestDefinitions();
408        if (rests.isEmpty()) {
409            return null;
410        }
411
412        // use a routes definition to dump the rests
413        RestsDefinition def = new RestsDefinition();
414        def.setRests(rests);
415
416        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
417        return ecc.getModelToXMLDumper().dumpModelAsXml(context, def, resolvePlaceholders, false);
418    }
419
420    @Override
421    public String dumpRoutesAsXml() throws Exception {
422        return dumpRoutesAsXml(false, false);
423    }
424
425    @Override
426    public String dumpRoutesAsXml(boolean resolvePlaceholders) throws Exception {
427        return dumpRoutesAsXml(resolvePlaceholders, false);
428    }
429
430    @Override
431    public String dumpRoutesAsXml(boolean resolvePlaceholders, boolean resolveDelegateEndpoints) throws Exception {
432        List<RouteDefinition> routes = context.getExtension(Model.class).getRouteDefinitions();
433        if (routes.isEmpty()) {
434            return null;
435        }
436
437        // use a routes definition to dump the routes
438        RoutesDefinition def = new RoutesDefinition();
439        def.setRoutes(routes);
440
441        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
442        return ecc.getModelToXMLDumper().dumpModelAsXml(context, def, resolvePlaceholders, resolveDelegateEndpoints);
443    }
444
445    @Override
446    public void addOrUpdateRoutesFromXml(String xml) throws Exception {
447        // do not decode so we function as before
448        addOrUpdateRoutesFromXml(xml, false);
449    }
450
451    @Override
452    public void addOrUpdateRoutesFromXml(String xml, boolean urlDecode) throws Exception {
453        // decode String as it may have been encoded, from its xml source
454        if (urlDecode) {
455            xml = URLDecoder.decode(xml, "UTF-8");
456        }
457
458        InputStream is = context.getTypeConverter().mandatoryConvertTo(InputStream.class, xml);
459        try {
460            // add will remove existing route first
461            ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
462            RoutesDefinition routes = (RoutesDefinition) ecc.getXMLRoutesDefinitionLoader().loadRoutesDefinition(ecc, is);
463            context.getExtension(Model.class).addRouteDefinitions(routes.getRoutes());
464        } catch (Exception e) {
465            // log the error as warn as the management api may be invoked remotely over JMX which does not propagate such exception
466            String msg = "Error updating routes from xml: " + xml + " due: " + e.getMessage();
467            LOG.warn(msg, e);
468            throw e;
469        }
470    }
471
472    @Override
473    public String dumpRoutesStatsAsXml(boolean fullStats, boolean includeProcessors) throws Exception {
474        StringBuilder sb = new StringBuilder();
475        sb.append("<camelContextStat").append(String.format(" id=\"%s\" state=\"%s\"", getCamelId(), getState()));
476        // use substring as we only want the attributes
477        String stat = dumpStatsAsXml(fullStats);
478        sb.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\"");
479        sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
480
481        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
482        if (server != null) {
483            // gather all the routes for this CamelContext, which requires JMX
484            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
485            ObjectName query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*");
486            Set<ObjectName> routes = server.queryNames(query, null);
487
488            List<ManagedProcessorMBean> processors = new ArrayList<>();
489            if (includeProcessors) {
490                // gather all the processors for this CamelContext, which requires JMX
491                query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
492                Set<ObjectName> names = server.queryNames(query, null);
493                for (ObjectName on : names) {
494                    ManagedProcessorMBean processor = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedProcessorMBean.class);
495                    processors.add(processor);
496                }
497            }
498            processors.sort(new OrderProcessorMBeans());
499
500            // loop the routes, and append the processor stats if needed
501            sb.append("  <routeStats>\n");
502            for (ObjectName on : routes) {
503                ManagedRouteMBean route = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedRouteMBean.class);
504                sb.append("    <routeStat").append(String.format(" id=\"%s\" state=\"%s\"", route.getRouteId(), route.getState()));
505                // use substring as we only want the attributes
506                stat = route.dumpStatsAsXml(fullStats);
507                sb.append(" exchangesInflight=\"").append(route.getExchangesInflight()).append("\"");
508                sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
509
510                // add processor details if needed
511                if (includeProcessors) {
512                    sb.append("      <processorStats>\n");
513                    for (ManagedProcessorMBean processor : processors) {
514                        // the processor must belong to this route
515                        if (route.getRouteId().equals(processor.getRouteId())) {
516                            sb.append("        <processorStat").append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\"", processor.getProcessorId(), processor.getIndex(), processor.getState()));
517                            // use substring as we only want the attributes
518                            stat = processor.dumpStatsAsXml(fullStats);
519                            sb.append(" exchangesInflight=\"").append(processor.getExchangesInflight()).append("\"");
520                            sb.append(" ").append(stat, 7, stat.length()).append("\n");
521                        }
522                    }
523                    sb.append("      </processorStats>\n");
524                }
525                sb.append("    </routeStat>\n");
526            }
527            sb.append("  </routeStats>\n");
528        }
529
530        sb.append("</camelContextStat>");
531        return sb.toString();
532    }
533
534    @Override
535    public String dumpStepStatsAsXml(boolean fullStats) throws Exception {
536        StringBuilder sb = new StringBuilder();
537        sb.append("<camelContextStat").append(String.format(" id=\"%s\" state=\"%s\"", getCamelId(), getState()));
538        // use substring as we only want the attributes
539        String stat = dumpStatsAsXml(fullStats);
540        sb.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\"");
541        sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
542
543        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
544        if (server != null) {
545            // gather all the routes for this CamelContext, which requires JMX
546            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
547            ObjectName query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*");
548            Set<ObjectName> routes = server.queryNames(query, null);
549
550            List<ManagedProcessorMBean> steps = new ArrayList<>();
551            // gather all the steps for this CamelContext, which requires JMX
552            query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=steps,*");
553            Set<ObjectName> names = server.queryNames(query, null);
554            for (ObjectName on : names) {
555                ManagedStepMBean step = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedStepMBean.class);
556                steps.add(step);
557            }
558            steps.sort(new OrderProcessorMBeans());
559
560            // loop the routes, and append the processor stats if needed
561            sb.append("  <routeStats>\n");
562            for (ObjectName on : routes) {
563                ManagedRouteMBean route = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedRouteMBean.class);
564                sb.append("    <routeStat").append(String.format(" id=\"%s\" state=\"%s\"", route.getRouteId(), route.getState()));
565                // use substring as we only want the attributes
566                stat = route.dumpStatsAsXml(fullStats);
567                sb.append(" exchangesInflight=\"").append(route.getExchangesInflight()).append("\"");
568                sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
569
570                // add steps details if needed
571                sb.append("      <stepStats>\n");
572                for (ManagedProcessorMBean processor : steps) {
573                    // the step must belong to this route
574                    if (route.getRouteId().equals(processor.getRouteId())) {
575                        sb.append("        <stepStat").append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\"", processor.getProcessorId(), processor.getIndex(), processor.getState()));
576                        // use substring as we only want the attributes
577                        stat = processor.dumpStatsAsXml(fullStats);
578                        sb.append(" exchangesInflight=\"").append(processor.getExchangesInflight()).append("\"");
579                        sb.append(" ").append(stat, 7, stat.length()).append("\n");
580                    }
581                    sb.append("      </stepStats>\n");
582                }
583                sb.append("    </stepStat>\n");
584            }
585            sb.append("  </routeStats>\n");
586        }
587
588        sb.append("</camelContextStat>");
589        return sb.toString();
590    }
591
592    @Override
593    public String dumpRoutesCoverageAsXml() throws Exception {
594        StringBuilder sb = new StringBuilder();
595        sb.append("<camelContextRouteCoverage")
596                .append(String.format(" id=\"%s\" exchangesTotal=\"%s\" totalProcessingTime=\"%s\"", getCamelId(), getExchangesTotal(), getTotalProcessingTime()))
597                .append(">\n");
598
599        String xml = dumpRoutesAsXml();
600        if (xml != null) {
601            // use the coverage xml parser to dump the routes and enrich with coverage stats
602            Document dom = RouteCoverageXmlParser.parseXml(context, new ByteArrayInputStream(xml.getBytes()));
603            // convert dom back to xml
604            String converted = context.getTypeConverter().convertTo(String.class, dom);
605            sb.append(converted);
606        }
607
608        sb.append("\n</camelContextRouteCoverage>");
609        return sb.toString();
610    }
611
612    @Override
613    public boolean createEndpoint(String uri) throws Exception {
614        if (context.hasEndpoint(uri) != null) {
615            // endpoint already exists
616            return false;
617        }
618
619        Endpoint endpoint = context.getEndpoint(uri);
620        if (endpoint != null) {
621            // ensure endpoint is registered, as the management strategy could have been configured to not always
622            // register new endpoints in JMX, so we need to check if its registered, and if not register it manually
623            ObjectName on = context.getManagementStrategy().getManagementObjectNameStrategy().getObjectNameForEndpoint(endpoint);
624            if (on != null && !context.getManagementStrategy().getManagementAgent().isRegistered(on)) {
625                // register endpoint as mbean
626                Object me = context.getManagementStrategy().getManagementObjectStrategy().getManagedObjectForEndpoint(context, endpoint);
627                context.getManagementStrategy().getManagementAgent().register(me, on);
628            }
629            return true;
630        } else {
631            return false;
632        }
633    }
634
635    @Override
636    public int removeEndpoints(String pattern) throws Exception {
637        // endpoints is always removed from JMX if removed from context
638        Collection<Endpoint> removed = context.removeEndpoints(pattern);
639        return removed.size();
640    }
641
642    @Override
643    public String componentParameterJsonSchema(String componentName) throws Exception {
644        return context.adapt(CatalogCamelContext.class).getComponentParameterJsonSchema(componentName);
645    }
646
647    @Override
648    public String dataFormatParameterJsonSchema(String dataFormatName) throws Exception {
649        return context.adapt(CatalogCamelContext.class).getDataFormatParameterJsonSchema(dataFormatName);
650    }
651
652    @Override
653    public String languageParameterJsonSchema(String languageName) throws Exception {
654        return context.adapt(CatalogCamelContext.class).getLanguageParameterJsonSchema(languageName);
655    }
656
657    @Override
658    public String eipParameterJsonSchema(String eipName) throws Exception {
659        return context.adapt(CatalogCamelContext.class).getEipParameterJsonSchema(eipName);
660    }
661
662    @Override
663    public void reset(boolean includeRoutes) throws Exception {
664        reset();
665
666        // and now reset all routes for this route
667        if (includeRoutes) {
668            MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
669            if (server != null) {
670                String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
671                ObjectName query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*");
672                Set<ObjectName> names = server.queryNames(query, null);
673                for (ObjectName name : names) {
674                    server.invoke(name, "reset", new Object[]{true}, new String[]{"boolean"});
675                }
676            }
677        }
678    }
679
680    /**
681     * Used for sorting the processor mbeans accordingly to their index.
682     */
683    private static final class OrderProcessorMBeans implements Comparator<ManagedProcessorMBean> {
684
685        @Override
686        public int compare(ManagedProcessorMBean o1, ManagedProcessorMBean o2) {
687            return o1.getIndex().compareTo(o2.getIndex());
688        }
689    }
690
691}