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.ExtendedCamelContext;
045import org.apache.camel.ManagementStatisticsLevel;
046import org.apache.camel.Route;
047import org.apache.camel.RuntimeCamelException;
048import org.apache.camel.ServiceStatus;
049import org.apache.camel.TimerListener;
050import org.apache.camel.api.management.ManagedResource;
051import org.apache.camel.api.management.mbean.CamelOpenMBeanTypes;
052import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
053import org.apache.camel.api.management.mbean.ManagedRouteMBean;
054import org.apache.camel.api.management.mbean.ManagedStepMBean;
055import org.apache.camel.api.management.mbean.RouteError;
056import org.apache.camel.model.Model;
057import org.apache.camel.model.ModelCamelContext;
058import org.apache.camel.model.RouteDefinition;
059import org.apache.camel.spi.InflightRepository;
060import org.apache.camel.spi.ManagementStrategy;
061import org.apache.camel.spi.RoutePolicy;
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, false);
387    }
388
389    @Override
390    public String dumpRouteAsXml(boolean resolvePlaceholders) throws Exception {
391        return dumpRouteAsXml(resolvePlaceholders, false);
392    }
393
394    @Override
395    public String dumpRouteAsXml(boolean resolvePlaceholders, boolean resolveDelegateEndpoints) throws Exception {
396        String id = route.getId();
397        RouteDefinition def = context.getExtension(Model.class).getRouteDefinition(id);
398        if (def != null) {
399            ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
400            return ecc.getModelToXMLDumper().dumpModelAsXml(context, def, resolvePlaceholders, resolveDelegateEndpoints);
401        }
402
403        return null;
404    }
405
406    @Override
407    public String dumpRouteStatsAsXml(boolean fullStats, boolean includeProcessors) throws Exception {
408        // in this logic we need to calculate the accumulated processing time for the processor in the route
409        // and hence why the logic is a bit more complicated to do this, as we need to calculate that from
410        // the bottom -> top of the route but this information is valuable for profiling routes
411        StringBuilder sb = new StringBuilder();
412
413        // need to calculate this value first, as we need that value for the route stat
414        long processorAccumulatedTime = 0L;
415
416        // gather all the processors for this route, which requires JMX
417        if (includeProcessors) {
418            sb.append("  <processorStats>\n");
419            MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
420            if (server != null) {
421                // get all the processor mbeans and sort them accordingly to their index
422                String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
423                ObjectName query = ObjectName.getInstance(
424                        jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
425                Set<ObjectName> names = server.queryNames(query, null);
426                List<ManagedProcessorMBean> mps = new ArrayList<>();
427                for (ObjectName on : names) {
428                    ManagedProcessorMBean processor = context.getManagementStrategy().getManagementAgent().newProxyClient(on,
429                            ManagedProcessorMBean.class);
430
431                    // the processor must belong to this route
432                    if (getRouteId().equals(processor.getRouteId())) {
433                        mps.add(processor);
434                    }
435                }
436                mps.sort(new OrderProcessorMBeans());
437
438                // walk the processors in reverse order, and calculate the accumulated total time
439                Map<String, Long> accumulatedTimes = new HashMap<>();
440                Collections.reverse(mps);
441                for (ManagedProcessorMBean processor : mps) {
442                    processorAccumulatedTime += processor.getTotalProcessingTime();
443                    accumulatedTimes.put(processor.getProcessorId(), processorAccumulatedTime);
444                }
445                // and reverse back again
446                Collections.reverse(mps);
447
448                // and now add the sorted list of processors to the xml output
449                for (ManagedProcessorMBean processor : mps) {
450                    int line = processor.getSourceLineNumber() != null ? processor.getSourceLineNumber() : -1;
451                    sb.append("    <processorStat")
452                            .append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\" sourceLineNumber=\"%s\"",
453                                    processor.getProcessorId(), processor.getIndex(), processor.getState(), line));
454                    // do we have an accumulated time then append that
455                    Long accTime = accumulatedTimes.get(processor.getProcessorId());
456                    if (accTime != null) {
457                        sb.append(" accumulatedProcessingTime=\"").append(accTime).append("\"");
458                    }
459                    // use substring as we only want the attributes
460                    sb.append(" ").append(processor.dumpStatsAsXml(fullStats).substring(7)).append("\n");
461                }
462            }
463            sb.append("  </processorStats>\n");
464        }
465
466        // route self time is route total - processor accumulated total)
467        long routeSelfTime = getTotalProcessingTime() - processorAccumulatedTime;
468        if (routeSelfTime < 0) {
469            // ensure we don't calculate that as negative
470            routeSelfTime = 0;
471        }
472
473        StringBuilder answer = new StringBuilder();
474        answer.append("<routeStat").append(String.format(" id=\"%s\"", route.getId()))
475                .append(String.format(" state=\"%s\"", getState()));
476        if (sourceLocation != null) {
477            answer.append(String.format(" sourceLocation=\"%s\"", getSourceLocation()));
478        }
479        // use substring as we only want the attributes
480        String stat = dumpStatsAsXml(fullStats);
481        answer.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\"");
482        answer.append(" selfProcessingTime=\"").append(routeSelfTime).append("\"");
483        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
484        if (oldest == null) {
485            answer.append(" oldestInflightExchangeId=\"\"");
486            answer.append(" oldestInflightDuration=\"\"");
487        } else {
488            answer.append(" oldestInflightExchangeId=\"").append(oldest.getExchange().getExchangeId()).append("\"");
489            answer.append(" oldestInflightDuration=\"").append(oldest.getDuration()).append("\"");
490        }
491        answer.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
492
493        if (includeProcessors) {
494            answer.append(sb);
495        }
496
497        answer.append("</routeStat>");
498        return answer.toString();
499    }
500
501    @Override
502    public String dumpStepStatsAsXml(boolean fullStats) throws Exception {
503        // in this logic we need to calculate the accumulated processing time for the processor in the route
504        // and hence why the logic is a bit more complicated to do this, as we need to calculate that from
505        // the bottom -> top of the route but this information is valuable for profiling routes
506        StringBuilder sb = new StringBuilder();
507
508        // gather all the steps for this route, which requires JMX
509        sb.append("  <stepStats>\n");
510        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
511        if (server != null) {
512            // get all the processor mbeans and sort them accordingly to their index
513            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
514            ObjectName query = ObjectName
515                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=steps,*");
516            Set<ObjectName> names = server.queryNames(query, null);
517            List<ManagedStepMBean> mps = new ArrayList<>();
518            for (ObjectName on : names) {
519                ManagedStepMBean step
520                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedStepMBean.class);
521
522                // the step must belong to this route
523                if (getRouteId().equals(step.getRouteId())) {
524                    mps.add(step);
525                }
526            }
527            mps.sort(new OrderProcessorMBeans());
528
529            // and now add the sorted list of steps to the xml output
530            for (ManagedStepMBean step : mps) {
531                int line = step.getSourceLineNumber() != null ? step.getSourceLineNumber() : -1;
532                sb.append("    <stepStat")
533                        .append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\" sourceLineNumber=\"%s\"",
534                                step.getProcessorId(),
535                                step.getIndex(), step.getState(), line));
536                // use substring as we only want the attributes
537                sb.append(" ").append(step.dumpStatsAsXml(fullStats).substring(7)).append("\n");
538            }
539        }
540        sb.append("  </stepStats>\n");
541
542        StringBuilder answer = new StringBuilder();
543        answer.append("<routeStat").append(String.format(" id=\"%s\"", route.getId()))
544                .append(String.format(" state=\"%s\"", getState()));
545        if (sourceLocation != null) {
546            answer.append(String.format(" sourceLocation=\"%s\"", getSourceLocation()));
547        }
548        // use substring as we only want the attributes
549        String stat = dumpStatsAsXml(fullStats);
550        answer.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\"");
551        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
552        if (oldest == null) {
553            answer.append(" oldestInflightExchangeId=\"\"");
554            answer.append(" oldestInflightDuration=\"\"");
555        } else {
556            answer.append(" oldestInflightExchangeId=\"").append(oldest.getExchange().getExchangeId()).append("\"");
557            answer.append(" oldestInflightDuration=\"").append(oldest.getDuration()).append("\"");
558        }
559        answer.append(" ").append(stat, 7, stat.length() - 2).append(">\n");
560
561        answer.append(sb);
562
563        answer.append("</routeStat>");
564        return answer.toString();
565    }
566
567    @Override
568    public String dumpRouteSourceLocationsAsXml() throws Exception {
569        StringBuilder sb = new StringBuilder();
570        sb.append("<routeLocations>");
571
572        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
573        if (server != null) {
574            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
575            List<ManagedProcessorMBean> processors = new ArrayList<>();
576            // gather all the processors for this CamelContext, which requires JMX
577            ObjectName query = ObjectName
578                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
579            Set<ObjectName> names = server.queryNames(query, null);
580            for (ObjectName on : names) {
581                ManagedProcessorMBean processor
582                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedProcessorMBean.class);
583                // the processor must belong to this route
584                if (getRouteId().equals(processor.getRouteId())) {
585                    processors.add(processor);
586                }
587            }
588            processors.sort(new OrderProcessorMBeans());
589
590            // grab route consumer
591            RouteDefinition rd = context.adapt(ModelCamelContext.class).getRouteDefinition(route.getRouteId());
592            if (rd != null) {
593                String id = rd.getRouteId();
594                int line = rd.getInput().getLineNumber();
595                String location = getSourceLocation() != null ? getSourceLocation() : "";
596                sb.append("\n    <routeLocation")
597                        .append(String.format(
598                                " routeId=\"%s\" id=\"%s\" index=\"%s\" sourceLocation=\"%s\" sourceLineNumber=\"%s\"/>",
599                                route.getRouteId(), id, 0, location, line));
600            }
601            for (ManagedProcessorMBean processor : processors) {
602                // the step must belong to this route
603                if (route.getRouteId().equals(processor.getRouteId())) {
604                    int line = processor.getSourceLineNumber() != null ? processor.getSourceLineNumber() : -1;
605                    String location = processor.getSourceLocation() != null ? processor.getSourceLocation() : "";
606                    sb.append("\n    <routeLocation")
607                            .append(String.format(
608                                    " routeId=\"%s\" id=\"%s\" index=\"%s\" sourceLocation=\"%s\" sourceLineNumber=\"%s\"/>",
609                                    route.getRouteId(), processor.getProcessorId(), processor.getIndex(), location, line));
610                }
611            }
612        }
613        sb.append("\n</routeLocations>");
614        return sb.toString();
615    }
616
617    @Override
618    public void reset(boolean includeProcessors) throws Exception {
619        reset();
620        load.reset();
621        thp.reset();
622
623        // and now reset all processors for this route
624        if (includeProcessors) {
625            MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
626            if (server != null) {
627                // get all the processor mbeans and sort them accordingly to their index
628                String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
629                ObjectName query = ObjectName.getInstance(
630                        jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
631                QueryExp queryExp = Query.match(new AttributeValueExp("RouteId"), new StringValueExp(getRouteId()));
632                Set<ObjectName> names = server.queryNames(query, queryExp);
633                for (ObjectName name : names) {
634                    server.invoke(name, "reset", null, null);
635                }
636            }
637        }
638    }
639
640    @Override
641    public boolean equals(Object o) {
642        return this == o || o != null && getClass() == o.getClass() && route.equals(((ManagedRoute) o).route);
643    }
644
645    @Override
646    public int hashCode() {
647        return route.hashCode();
648    }
649
650    private InflightRepository.InflightExchange getOldestInflightEntry() {
651        return getContext().getInflightRepository().oldest(getRouteId());
652    }
653
654    @Override
655    public Long getOldestInflightDuration() {
656        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
657        if (oldest == null) {
658            return null;
659        } else {
660            return oldest.getDuration();
661        }
662    }
663
664    @Override
665    public String getOldestInflightExchangeId() {
666        InflightRepository.InflightExchange oldest = getOldestInflightEntry();
667        if (oldest == null) {
668            return null;
669        } else {
670            return oldest.getExchange().getExchangeId();
671        }
672    }
673
674    @Override
675    public Boolean getHasRouteController() {
676        return route.getRouteController() != null;
677    }
678
679    @Override
680    public RouteError getLastError() {
681        org.apache.camel.spi.RouteError error = route.getLastError();
682        if (error == null) {
683            return null;
684        } else {
685            return new RouteError() {
686                @Override
687                public Phase getPhase() {
688                    if (error.getPhase() != null) {
689                        switch (error.getPhase()) {
690                            case START:
691                                return Phase.START;
692                            case STOP:
693                                return Phase.STOP;
694                            case SUSPEND:
695                                return Phase.SUSPEND;
696                            case RESUME:
697                                return Phase.RESUME;
698                            case SHUTDOWN:
699                                return Phase.SHUTDOWN;
700                            case REMOVE:
701                                return Phase.REMOVE;
702                            default:
703                                throw new IllegalStateException();
704                        }
705                    }
706                    return null;
707                }
708
709                @Override
710                public Throwable getException() {
711                    return error.getException();
712                }
713            };
714        }
715    }
716
717    @Override
718    public Collection<String> processorIds() throws Exception {
719        List<String> ids = new ArrayList<>();
720
721        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
722        if (server != null) {
723            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
724            // gather all the processors for this CamelContext, which requires JMX
725            ObjectName query = ObjectName
726                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
727            Set<ObjectName> names = server.queryNames(query, null);
728            for (ObjectName on : names) {
729                ManagedProcessorMBean processor
730                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedProcessorMBean.class);
731                // the processor must belong to this route
732                if (getRouteId().equals(processor.getRouteId())) {
733                    ids.add(processor.getProcessorId());
734                }
735            }
736        }
737
738        return ids;
739    }
740
741    private Integer getInflightExchanges() {
742        return (int) super.getExchangesInflight();
743    }
744
745    /**
746     * Used for sorting the processor mbeans accordingly to their index.
747     */
748    private static final class OrderProcessorMBeans implements Comparator<ManagedProcessorMBean>, Serializable {
749
750        @Override
751        public int compare(ManagedProcessorMBean o1, ManagedProcessorMBean o2) {
752            return o1.getIndex().compareTo(o2.getIndex());
753        }
754    }
755}