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