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