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