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.InputStream;
020import java.io.Serializable;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.RejectedExecutionException;
030import java.util.concurrent.TimeUnit;
031
032import javax.management.AttributeValueExp;
033import javax.management.MBeanServer;
034import javax.management.ObjectName;
035import javax.management.Query;
036import javax.management.QueryExp;
037import javax.management.StringValueExp;
038import javax.management.openmbean.CompositeData;
039import javax.management.openmbean.CompositeDataSupport;
040import javax.management.openmbean.CompositeType;
041import javax.management.openmbean.TabularData;
042import javax.management.openmbean.TabularDataSupport;
043
044import org.apache.camel.CamelContext;
045import org.apache.camel.ExtendedCamelContext;
046import org.apache.camel.ManagementStatisticsLevel;
047import org.apache.camel.Route;
048import org.apache.camel.RuntimeCamelException;
049import org.apache.camel.ServiceStatus;
050import org.apache.camel.TimerListener;
051import org.apache.camel.api.management.ManagedResource;
052import org.apache.camel.api.management.mbean.CamelOpenMBeanTypes;
053import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
054import org.apache.camel.api.management.mbean.ManagedRouteMBean;
055import org.apache.camel.api.management.mbean.ManagedStepMBean;
056import org.apache.camel.api.management.mbean.RouteError;
057import org.apache.camel.model.Model;
058import org.apache.camel.model.ModelCamelContext;
059import org.apache.camel.model.RouteDefinition;
060import org.apache.camel.model.RoutesDefinition;
061import org.apache.camel.spi.InflightRepository;
062import org.apache.camel.spi.ManagementStrategy;
063import org.apache.camel.spi.RoutePolicy;
064import org.apache.camel.support.PluginHelper;
065import org.apache.camel.util.ObjectHelper;
066import org.apache.camel.xml.jaxb.JaxbHelper;
067import org.slf4j.Logger;
068import org.slf4j.LoggerFactory;
069
070@ManagedResource(description = "Managed Route")
071public class ManagedRoute extends ManagedPerformanceCounter implements TimerListener, ManagedRouteMBean {
072
073    public static final String VALUE_UNKNOWN = "Unknown";
074
075    private static final Logger LOG = LoggerFactory.getLogger(ManagedRoute.class);
076
077    protected final Route route;
078    protected final String description;
079    protected final String configurationId;
080    protected final String sourceLocation;
081    protected final String sourceLocationShort;
082    protected final CamelContext context;
083    private final LoadTriplet load = new LoadTriplet();
084    private final LoadThroughput thp = new LoadThroughput();
085    private final String jmxDomain;
086
087    public ManagedRoute(CamelContext context, Route route) {
088        this.route = route;
089        this.context = context;
090        this.description = route.getDescription();
091        this.configurationId = route.getConfigurationId();
092        this.sourceLocation = route.getSourceLocation();
093        this.sourceLocationShort = route.getSourceLocationShort();
094        this.jmxDomain = context.getManagementStrategy().getManagementAgent().getMBeanObjectDomainName();
095    }
096
097    @Override
098    public void init(ManagementStrategy strategy) {
099        super.init(strategy);
100        boolean enabled
101                = context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != ManagementStatisticsLevel.Off;
102        setStatisticsEnabled(enabled);
103    }
104
105    public Route getRoute() {
106        return route;
107    }
108
109    public CamelContext getContext() {
110        return context;
111    }
112
113    @Override
114    public String getRouteId() {
115        String id = route.getId();
116        if (id == null) {
117            id = VALUE_UNKNOWN;
118        }
119        return id;
120    }
121
122    @Override
123    public String getNodePrefixId() {
124        return route.getNodePrefixId();
125    }
126
127    @Override
128    public String getRouteGroup() {
129        return route.getGroup();
130    }
131
132    @Override
133    public TabularData getRouteProperties() {
134        try {
135            final Map<String, Object> properties = route.getProperties();
136            final TabularData answer = new TabularDataSupport(CamelOpenMBeanTypes.camelRoutePropertiesTabularType());
137            final CompositeType ct = CamelOpenMBeanTypes.camelRoutePropertiesCompositeType();
138
139            // gather route properties
140            for (Map.Entry<String, Object> entry : properties.entrySet()) {
141                final String key = entry.getKey();
142                final String val = context.getTypeConverter().convertTo(String.class, entry.getValue());
143
144                CompositeData data = new CompositeDataSupport(
145                        ct,
146                        new String[] { "key", "value" },
147                        new Object[] { key, val });
148
149                answer.put(data);
150            }
151            return answer;
152        } catch (Exception e) {
153            throw RuntimeCamelException.wrapRuntimeCamelException(e);
154        }
155    }
156
157    @Override
158    public String getDescription() {
159        return description;
160    }
161
162    @Override
163    public String getSourceLocation() {
164        return sourceLocation;
165    }
166
167    @Override
168    public String getSourceLocationShort() {
169        return sourceLocationShort;
170    }
171
172    @Override
173    public String getRouteConfigurationId() {
174        return configurationId;
175    }
176
177    @Override
178    public String getEndpointUri() {
179        if (route.getEndpoint() != null) {
180            return route.getEndpoint().getEndpointUri();
181        }
182        return VALUE_UNKNOWN;
183    }
184
185    @Override
186    public String getState() {
187        // must use String type to be sure remote JMX can read the attribute without requiring Camel classes.
188        ServiceStatus status = context.getRouteController().getRouteStatus(route.getId());
189        // if no status exists then its stopped
190        if (status == null) {
191            status = ServiceStatus.Stopped;
192        }
193        return status.name();
194    }
195
196    @Override
197    public String getUptime() {
198        return route.getUptime();
199    }
200
201    @Override
202    public long getUptimeMillis() {
203        return route.getUptimeMillis();
204    }
205
206    @Override
207    public String getCamelId() {
208        return context.getName();
209    }
210
211    @Override
212    public String getCamelManagementName() {
213        return context.getManagementName();
214    }
215
216    @Override
217    public Boolean getTracing() {
218        return route.isTracing();
219    }
220
221    @Override
222    public void setTracing(Boolean tracing) {
223        route.setTracing(tracing);
224    }
225
226    @Override
227    public Boolean getMessageHistory() {
228        return route.isMessageHistory();
229    }
230
231    @Override
232    public Boolean getLogMask() {
233        return route.isLogMask();
234    }
235
236    @Override
237    public String getRoutePolicyList() {
238        List<RoutePolicy> policyList = route.getRoutePolicyList();
239
240        if (policyList == null || policyList.isEmpty()) {
241            // return an empty string to have it displayed nicely in JMX consoles
242            return "";
243        }
244
245        StringBuilder sb = new StringBuilder();
246        for (int i = 0; i < policyList.size(); i++) {
247            RoutePolicy policy = policyList.get(i);
248            sb.append(policy.getClass().getSimpleName());
249            sb.append("(").append(ObjectHelper.getIdentityHashCode(policy)).append(")");
250            if (i < policyList.size() - 1) {
251                sb.append(", ");
252            }
253        }
254        return sb.toString();
255    }
256
257    @Override
258    public String getLoad01() {
259        double load1 = load.getLoad1();
260        if (Double.isNaN(load1)) {
261            // empty string if load statistics is disabled
262            return "";
263        } else {
264            return String.format("%.2f", load1);
265        }
266    }
267
268    @Override
269    public String getLoad05() {
270        double load5 = load.getLoad5();
271        if (Double.isNaN(load5)) {
272            // empty string if load statistics is disabled
273            return "";
274        } else {
275            return String.format("%.2f", load5);
276        }
277    }
278
279    @Override
280    public String getLoad15() {
281        double load15 = load.getLoad15();
282        if (Double.isNaN(load15)) {
283            // empty string if load statistics is disabled
284            return "";
285        } else {
286            return String.format("%.2f", load15);
287        }
288    }
289
290    @Override
291    public String getThroughput() {
292        double d = thp.getThroughput();
293        if (Double.isNaN(d)) {
294            // empty string if load statistics is disabled
295            return "";
296        } else {
297            return String.format("%.2f", d);
298        }
299    }
300
301    @Override
302    public void onTimer() {
303        load.update(getInflightExchanges());
304        thp.update(getExchangesTotal());
305    }
306
307    @Override
308    public void start() throws Exception {
309        if (!context.getStatus().isStarted()) {
310            throw new IllegalArgumentException("CamelContext is not started");
311        }
312        context.getRouteController().startRoute(getRouteId());
313    }
314
315    @Override
316    public void stop() throws Exception {
317        if (!context.getStatus().isStarted()) {
318            throw new IllegalArgumentException("CamelContext is not started");
319        }
320        context.getRouteController().stopRoute(getRouteId());
321    }
322
323    @Override
324    public void stopAndFail() throws Exception {
325        if (!context.getStatus().isStarted()) {
326            throw new IllegalArgumentException("CamelContext is not started");
327        }
328        Throwable cause = new RejectedExecutionException("Route " + getRouteId() + " is forced stopped and marked as failed");
329        context.getRouteController().stopRoute(getRouteId(), cause);
330    }
331
332    @Override
333    public void stop(long timeout) throws Exception {
334        if (!context.getStatus().isStarted()) {
335            throw new IllegalArgumentException("CamelContext is not started");
336        }
337        context.getRouteController().stopRoute(getRouteId(), timeout, TimeUnit.SECONDS);
338    }
339
340    @Override
341    public boolean stop(Long timeout, Boolean abortAfterTimeout) throws Exception {
342        if (!context.getStatus().isStarted()) {
343            throw new IllegalArgumentException("CamelContext is not started");
344        }
345        return context.getRouteController().stopRoute(getRouteId(), timeout, TimeUnit.SECONDS, abortAfterTimeout);
346    }
347
348    public void shutdown() throws Exception {
349        if (!context.getStatus().isStarted()) {
350            throw new IllegalArgumentException("CamelContext is not started");
351        }
352        String routeId = getRouteId();
353        context.getRouteController().stopRoute(routeId);
354        context.removeRoute(routeId);
355    }
356
357    public void shutdown(long timeout) throws Exception {
358        if (!context.getStatus().isStarted()) {
359            throw new IllegalArgumentException("CamelContext is not started");
360        }
361        String routeId = getRouteId();
362        context.getRouteController().stopRoute(routeId, timeout, TimeUnit.SECONDS);
363        context.removeRoute(routeId);
364    }
365
366    @Override
367    public boolean remove() throws Exception {
368        if (!context.getStatus().isStarted()) {
369            throw new IllegalArgumentException("CamelContext is not started");
370        }
371        return context.removeRoute(getRouteId());
372    }
373
374    @Override
375    public void restart() throws Exception {
376        restart(1);
377    }
378
379    @Override
380    public void restart(long delay) throws Exception {
381        stop();
382        if (delay > 0) {
383            try {
384                LOG.debug("Sleeping {} seconds before starting route: {}", delay, getRouteId());
385                Thread.sleep(delay * 1000);
386            } catch (InterruptedException e) {
387                LOG.info("Interrupted while waiting before starting the route");
388                Thread.currentThread().interrupt();
389            }
390        }
391        start();
392    }
393
394    @Override
395    public String dumpRouteAsXml() throws Exception {
396        return dumpRouteAsXml(false);
397    }
398
399    @Override
400    public String dumpRouteAsXml(boolean resolvePlaceholders) throws Exception {
401        return dumpRouteAsXml(resolvePlaceholders, true);
402    }
403
404    @Override
405    public String dumpRouteAsXml(boolean resolvePlaceholders, boolean generatedIds) throws Exception {
406        String id = route.getId();
407        RouteDefinition def = context.getCamelContextExtension().getContextPlugin(Model.class).getRouteDefinition(id);
408        if (def != null) {
409            // if we are debugging then ids is needed for the debugger
410            if (context.isDebugging()) {
411                generatedIds = true;
412            }
413            return PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, def, resolvePlaceholders, generatedIds);
414        }
415
416        return null;
417    }
418
419    @Override
420    public String dumpRouteAsYaml() throws Exception {
421        return dumpRouteAsYaml(false, false);
422    }
423
424    @Override
425    public String dumpRouteAsYaml(boolean resolvePlaceholders) throws Exception {
426        return dumpRouteAsYaml(resolvePlaceholders, false, true);
427    }
428
429    @Override
430    public String dumpRouteAsYaml(boolean resolvePlaceholders, boolean uriAsParameters) throws Exception {
431        return dumpRouteAsYaml(resolvePlaceholders, uriAsParameters, true);
432    }
433
434    @Override
435    public String dumpRouteAsYaml(boolean resolvePlaceholders, boolean uriAsParameters, boolean generatedIds) throws Exception {
436        String id = route.getId();
437        RouteDefinition def = context.getCamelContextExtension().getContextPlugin(Model.class).getRouteDefinition(id);
438        if (def != null) {
439            return PluginHelper.getModelToYAMLDumper(context).dumpModelAsYaml(context, def, resolvePlaceholders,
440                    uriAsParameters, generatedIds);
441        }
442
443        return null;
444    }
445
446    @Override
447    public String dumpRouteStatsAsXml(boolean fullStats, boolean includeProcessors) throws Exception {
448        // in this logic we need to calculate the accumulated processing time for the processor in the route
449        // and hence why the logic is a bit more complicated to do this, as we need to calculate that from
450        // the bottom -> top of the route but this information is valuable for profiling routes
451        StringBuilder sb = new StringBuilder();
452
453        // need to calculate this value first, as we need that value for the route stat
454        long processorAccumulatedTime = 0L;
455
456        // gather all the processors for this route, which requires JMX
457        if (includeProcessors) {
458            sb.append("  <processorStats>\n");
459            MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
460            if (server != null) {
461                // get all the processor mbeans and sort them accordingly to their index
462                String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
463                ObjectName query = ObjectName.getInstance(
464                        jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
465                Set<ObjectName> names = server.queryNames(query, null);
466                List<ManagedProcessorMBean> mps = new ArrayList<>();
467                for (ObjectName on : names) {
468                    ManagedProcessorMBean processor = context.getManagementStrategy().getManagementAgent().newProxyClient(on,
469                            ManagedProcessorMBean.class);
470
471                    // the processor must belong to this route
472                    if (getRouteId().equals(processor.getRouteId())) {
473                        mps.add(processor);
474                    }
475                }
476                mps.sort(new OrderProcessorMBeans());
477
478                // walk the processors in reverse order, and calculate the accumulated total time
479                Map<String, Long> accumulatedTimes = new HashMap<>();
480                Collections.reverse(mps);
481                for (ManagedProcessorMBean processor : mps) {
482                    processorAccumulatedTime += processor.getTotalProcessingTime();
483                    accumulatedTimes.put(processor.getProcessorId(), processorAccumulatedTime);
484                }
485                // and reverse back again
486                Collections.reverse(mps);
487
488                // and now add the sorted list of processors to the xml output
489                for (ManagedProcessorMBean processor : mps) {
490                    int line = processor.getSourceLineNumber() != null ? processor.getSourceLineNumber() : -1;
491                    sb.append("    <processorStat")
492                            .append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\" sourceLineNumber=\"%s\"",
493                                    processor.getProcessorId(), processor.getIndex(), processor.getState(), line));
494                    // do we have an accumulated time then append that
495                    Long accTime = accumulatedTimes.get(processor.getProcessorId());
496                    if (accTime != null) {
497                        sb.append(" accumulatedProcessingTime=\"").append(accTime).append("\"");
498                    }
499                    // use substring as we only want the attributes
500                    sb.append(" ").append(processor.dumpStatsAsXml(fullStats).substring(7)).append("\n");
501                }
502            }
503            sb.append("  </processorStats>\n");
504        }
505
506        // route self time is route total - processor accumulated total)
507        long routeSelfTime = getTotalProcessingTime() - processorAccumulatedTime;
508        if (routeSelfTime < 0) {
509            // ensure we don't calculate that as negative
510            routeSelfTime = 0;
511        }
512
513        StringBuilder answer = new StringBuilder();
514        answer.append("<routeStat").append(String.format(" id=\"%s\"", route.getId()))
515                .append(String.format(" state=\"%s\"", getState()));
516        if (sourceLocation != null) {
517            answer.append(String.format(" sourceLocation=\"%s\"", getSourceLocation()));
518        }
519        // use substring as we only want the attributes
520        String stat = dumpStatsAsXml(fullStats);
521        answer.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\"");
522        answer.append(" selfProcessingTime=\"").append(routeSelfTime).append("\"");
523        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
524        if (oldest == null) {
525            answer.append(" oldestInflightExchangeId=\"\"");
526            answer.append(" oldestInflightDuration=\"\"");
527        } else {
528            answer.append(" oldestInflightExchangeId=\"").append(oldest.getExchange().getExchangeId()).append("\"");
529            answer.append(" oldestInflightDuration=\"").append(oldest.getDuration()).append("\"");
530        }
531        answer.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
532
533        if (includeProcessors) {
534            answer.append(sb);
535        }
536
537        answer.append("</routeStat>");
538        return answer.toString();
539    }
540
541    @Override
542    public String dumpStepStatsAsXml(boolean fullStats) throws Exception {
543        // in this logic we need to calculate the accumulated processing time for the processor in the route
544        // and hence why the logic is a bit more complicated to do this, as we need to calculate that from
545        // the bottom -> top of the route but this information is valuable for profiling routes
546        StringBuilder sb = new StringBuilder();
547
548        // gather all the steps for this route, which requires JMX
549        sb.append("  <stepStats>\n");
550        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
551        if (server != null) {
552            // get all the processor mbeans and sort them accordingly to their index
553            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
554            ObjectName query = ObjectName
555                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=steps,*");
556            Set<ObjectName> names = server.queryNames(query, null);
557            List<ManagedStepMBean> mps = new ArrayList<>();
558            for (ObjectName on : names) {
559                ManagedStepMBean step
560                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedStepMBean.class);
561
562                // the step must belong to this route
563                if (getRouteId().equals(step.getRouteId())) {
564                    mps.add(step);
565                }
566            }
567            mps.sort(new OrderProcessorMBeans());
568
569            // and now add the sorted list of steps to the xml output
570            for (ManagedStepMBean step : mps) {
571                int line = step.getSourceLineNumber() != null ? step.getSourceLineNumber() : -1;
572                sb.append("    <stepStat")
573                        .append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\" sourceLineNumber=\"%s\"",
574                                step.getProcessorId(),
575                                step.getIndex(), step.getState(), line));
576                // use substring as we only want the attributes
577                sb.append(" ").append(step.dumpStatsAsXml(fullStats).substring(7)).append("\n");
578            }
579        }
580        sb.append("  </stepStats>\n");
581
582        StringBuilder answer = new StringBuilder();
583        answer.append("<routeStat").append(String.format(" id=\"%s\"", route.getId()))
584                .append(String.format(" state=\"%s\"", getState()));
585        if (sourceLocation != null) {
586            answer.append(String.format(" sourceLocation=\"%s\"", getSourceLocation()));
587        }
588        // use substring as we only want the attributes
589        String stat = dumpStatsAsXml(fullStats);
590        answer.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\"");
591        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
592        if (oldest == null) {
593            answer.append(" oldestInflightExchangeId=\"\"");
594            answer.append(" oldestInflightDuration=\"\"");
595        } else {
596            answer.append(" oldestInflightExchangeId=\"").append(oldest.getExchange().getExchangeId()).append("\"");
597            answer.append(" oldestInflightDuration=\"").append(oldest.getDuration()).append("\"");
598        }
599        answer.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
600
601        answer.append(sb);
602
603        answer.append("</routeStat>");
604        return answer.toString();
605    }
606
607    @Override
608    public String dumpRouteSourceLocationsAsXml() throws Exception {
609        StringBuilder sb = new StringBuilder();
610        sb.append("<routeLocations>");
611
612        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
613        if (server != null) {
614            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
615            List<ManagedProcessorMBean> processors = new ArrayList<>();
616            // gather all the processors for this CamelContext, which requires JMX
617            ObjectName query = ObjectName
618                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
619            Set<ObjectName> names = server.queryNames(query, null);
620            for (ObjectName on : names) {
621                ManagedProcessorMBean processor
622                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedProcessorMBean.class);
623                // the processor must belong to this route
624                if (getRouteId().equals(processor.getRouteId())) {
625                    processors.add(processor);
626                }
627            }
628            processors.sort(new OrderProcessorMBeans());
629
630            // grab route consumer
631            RouteDefinition rd = ((ModelCamelContext) context).getRouteDefinition(route.getRouteId());
632            if (rd != null) {
633                String id = rd.getRouteId();
634                int line = rd.getInput().getLineNumber();
635                String location = getSourceLocation() != null ? getSourceLocation() : "";
636                sb.append("\n    <routeLocation")
637                        .append(String.format(
638                                " routeId=\"%s\" id=\"%s\" index=\"%s\" sourceLocation=\"%s\" sourceLineNumber=\"%s\"/>",
639                                route.getRouteId(), id, 0, location, line));
640            }
641            for (ManagedProcessorMBean processor : processors) {
642                // the step must belong to this route
643                if (route.getRouteId().equals(processor.getRouteId())) {
644                    int line = processor.getSourceLineNumber() != null ? processor.getSourceLineNumber() : -1;
645                    String location = processor.getSourceLocation() != null ? processor.getSourceLocation() : "";
646                    sb.append("\n    <routeLocation")
647                            .append(String.format(
648                                    " routeId=\"%s\" id=\"%s\" index=\"%s\" sourceLocation=\"%s\" sourceLineNumber=\"%s\"/>",
649                                    route.getRouteId(), processor.getProcessorId(), processor.getIndex(), location, line));
650                }
651            }
652        }
653        sb.append("\n</routeLocations>");
654        return sb.toString();
655    }
656
657    @Override
658    public void reset(boolean includeProcessors) throws Exception {
659        reset();
660        load.reset();
661        thp.reset();
662
663        // and now reset all processors for this route
664        if (includeProcessors) {
665            MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
666            if (server != null) {
667                // get all the processor mbeans and sort them accordingly to their index
668                String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
669                ObjectName query = ObjectName.getInstance(
670                        jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
671                QueryExp queryExp = Query.match(new AttributeValueExp("RouteId"), new StringValueExp(getRouteId()));
672                Set<ObjectName> names = server.queryNames(query, queryExp);
673                for (ObjectName name : names) {
674                    server.invoke(name, "reset", null, null);
675                }
676            }
677        }
678    }
679
680    @Override
681    public void updateRouteFromXml(String xml) throws Exception {
682        // check whether this is allowed
683        if (!isUpdateRouteEnabled()) {
684            throw new IllegalAccessException("Updating route is not enabled");
685        }
686
687        // convert to model from xml
688        ExtendedCamelContext ecc = context.getCamelContextExtension();
689        InputStream is = context.getTypeConverter().convertTo(InputStream.class, xml);
690        RoutesDefinition routes = JaxbHelper.loadRoutesDefinition(context, is);
691        if (routes == null || routes.getRoutes().isEmpty()) {
692            return;
693        }
694        RouteDefinition def = routes.getRoutes().get(0);
695
696        // if the xml does not contain the route-id then we fix this by adding the actual route id
697        // this may be needed if the route-id was auto-generated, as the intend is to update this route
698        // and not add a new route, adding a new route, use the MBean operation on ManagedCamelContext instead.
699        if (ObjectHelper.isEmpty(def.getId())) {
700            def.setId(getRouteId());
701        } else if (!def.getId().equals(getRouteId())) {
702            throw new IllegalArgumentException(
703                    "Cannot update route from XML as routeIds does not match. routeId: "
704                                               + getRouteId() + ", routeId from XML: " + def.getId());
705        }
706
707        LOG.debug("Updating route: {} from xml: {}", def.getId(), xml);
708        try {
709            // add will remove existing route first
710            ecc.getContextPlugin(Model.class).addRouteDefinition(def);
711        } catch (Exception e) {
712            // log the error as warn as the management api may be invoked remotely over JMX which does not propagate such exception
713            String msg = "Error updating route: " + def.getId() + " from xml: " + xml + " due: " + e.getMessage();
714            LOG.warn(msg, e);
715            throw e;
716        }
717    }
718
719    @Override
720    public boolean isUpdateRouteEnabled() {
721        // check whether this is allowed
722        Boolean enabled = context.getManagementStrategy().getManagementAgent().getUpdateRouteEnabled();
723        return enabled != null ? enabled : false;
724    }
725
726    @Override
727    public boolean equals(Object o) {
728        return this == o || o != null && getClass() == o.getClass() && route.equals(((ManagedRoute) o).route);
729    }
730
731    @Override
732    public int hashCode() {
733        return route.hashCode();
734    }
735
736    private InflightRepository.InflightExchange getOldestInflightEntry() {
737        return getContext().getInflightRepository().oldest(getRouteId());
738    }
739
740    @Override
741    public Long getOldestInflightDuration() {
742        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
743        if (oldest == null) {
744            return null;
745        } else {
746            return oldest.getDuration();
747        }
748    }
749
750    @Override
751    public String getOldestInflightExchangeId() {
752        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
753        if (oldest == null) {
754            return null;
755        } else {
756            return oldest.getExchange().getExchangeId();
757        }
758    }
759
760    @Override
761    public Boolean getHasRouteController() {
762        return route.getRouteController() != null;
763    }
764
765    @Override
766    public RouteError getLastError() {
767        org.apache.camel.spi.RouteError error = route.getLastError();
768        if (error == null) {
769            return null;
770        } else {
771            return new RouteError() {
772                @Override
773                public Phase getPhase() {
774                    if (error.getPhase() != null) {
775                        switch (error.getPhase()) {
776                            case START:
777                                return Phase.START;
778                            case STOP:
779                                return Phase.STOP;
780                            case SUSPEND:
781                                return Phase.SUSPEND;
782                            case RESUME:
783                                return Phase.RESUME;
784                            case SHUTDOWN:
785                                return Phase.SHUTDOWN;
786                            case REMOVE:
787                                return Phase.REMOVE;
788                            default:
789                                throw new IllegalStateException();
790                        }
791                    }
792                    return null;
793                }
794
795                @Override
796                public Throwable getException() {
797                    return error.getException();
798                }
799            };
800        }
801    }
802
803    @Override
804    public Collection<String> processorIds() throws Exception {
805        List<String> ids = new ArrayList<>();
806
807        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
808        if (server != null) {
809            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
810            // gather all the processors for this CamelContext, which requires JMX
811            ObjectName query = ObjectName
812                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
813            Set<ObjectName> names = server.queryNames(query, null);
814            for (ObjectName on : names) {
815                ManagedProcessorMBean processor
816                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedProcessorMBean.class);
817                // the processor must belong to this route
818                if (getRouteId().equals(processor.getRouteId())) {
819                    ids.add(processor.getProcessorId());
820                }
821            }
822        }
823
824        return ids;
825    }
826
827    private Integer getInflightExchanges() {
828        return (int) super.getExchangesInflight();
829    }
830
831    /**
832     * Used for sorting the processor mbeans accordingly to their index.
833     */
834    private static final class OrderProcessorMBeans implements Comparator<ManagedProcessorMBean>, Serializable {
835
836        @Override
837        public int compare(ManagedProcessorMBean o1, ManagedProcessorMBean o2) {
838            return o1.getIndex().compareTo(o2.getIndex());
839        }
840    }
841}