CAMEL-8223: Inflight repository to allow browsing of current inflight exchanges
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/f812a45e Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/f812a45e Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/f812a45e Branch: refs/heads/master Commit: f812a45e16cf72ccd2feb7827b72427909fdaf56 Parents: 416654d Author: Claus Ibsen <davscl...@apache.org> Authored: Fri Jan 9 12:50:25 2015 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Fri Jan 9 13:41:46 2015 +0100 ---------------------------------------------------------------------- .../camel/impl/DefaultInflightRepository.java | 8 + .../apache/camel/spi/InflightRepository.java | 2 +- .../camel/commands/AbstractCamelCommand.java | 8 + .../commands/AbstractLocalCamelController.java | 53 +++++++ .../apache/camel/commands/CamelController.java | 11 ++ .../camel/commands/ContextInflightCommand.java | 150 +++++++++++++++++++ .../camel/commands/ContextInfoCommand.java | 8 - .../jolokia/DefaultJolokiaCamelController.java | 33 ++++ .../karaf/commands/CamelCommandSupport.java | 8 - .../camel/karaf/commands/ContextInflight.java | 43 ++++++ .../OSGI-INF/blueprint/camel-commands.xml | 9 ++ 11 files changed, 316 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/camel-core/src/main/java/org/apache/camel/impl/DefaultInflightRepository.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultInflightRepository.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultInflightRepository.java index 5abe8e8..ab0079b 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/DefaultInflightRepository.java +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultInflightRepository.java @@ -106,6 +106,14 @@ public class DefaultInflightRepository extends ServiceSupport implements Infligh return Long.compare(d1, d2); } }); + } else { + // else sort by exchange id + Collections.sort(values, new Comparator<Exchange>() { + @Override + public int compare(Exchange e1, Exchange e2) { + return e1.getExchangeId().compareTo(e2.getExchangeId()); + } + }); } for (Exchange exchange : values) { http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/camel-core/src/main/java/org/apache/camel/spi/InflightRepository.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/InflightRepository.java b/camel-core/src/main/java/org/apache/camel/spi/InflightRepository.java index 0fc313f..f7aad78 100644 --- a/camel-core/src/main/java/org/apache/camel/spi/InflightRepository.java +++ b/camel-core/src/main/java/org/apache/camel/spi/InflightRepository.java @@ -140,7 +140,7 @@ public interface InflightRepository extends StaticService { * * @param limit maximum number of entries to return * @param sortByLongestDuration to sort by the longest duration. Set to <tt>true</tt> to include the exchanges that has been inflight the longest time, - * set to <tt>false</tt> to include the exchanges in unspecified order. + * set to <tt>false</tt> to sort by exchange id */ Collection<InflightExchange> browse(int limit, boolean sortByLongestDuration); http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelCommand.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelCommand.java index 03fa4e3..55035b8 100644 --- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelCommand.java +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelCommand.java @@ -31,4 +31,12 @@ public abstract class AbstractCamelCommand implements CamelCommand { } } + public String safeNull(Object s) { + if (ObjectHelper.isEmpty(s)) { + return ""; + } else { + return s.toString(); + } + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java index 9ea4dc3..e2863a3 100644 --- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java @@ -17,6 +17,7 @@ package org.apache.camel.commands; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -28,6 +29,8 @@ import java.util.Properties; import java.util.Set; import javax.management.MBeanServer; import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; import org.apache.camel.CamelContext; import org.apache.camel.Endpoint; @@ -163,6 +166,56 @@ public abstract class AbstractLocalCamelController extends AbstractCamelControll return null; } + @SuppressWarnings("unchecked") + public List<Map<String, Object>> browseInflightExchanges(String camelContextName, int limit, boolean sortByLongestDuration) throws Exception { + CamelContext context = this.getLocalCamelContext(camelContextName); + if (context == null) { + return null; + } + + List<Map<String, Object>> answer = new ArrayList<Map<String, Object>>(); + + ManagementAgent agent = context.getManagementStrategy().getManagementAgent(); + if (agent != null) { + MBeanServer mBeanServer = agent.getMBeanServer(); + ObjectName on = null; + Set<ObjectName> set = mBeanServer.queryNames(new ObjectName(agent.getMBeanObjectDomainName() + ":type=services,name=DefaultInflightRepository,*"), null); + if (set.size() == 1) { + on = set.iterator().next(); + } + if (mBeanServer.isRegistered(on)) { + TabularData list = (TabularData) mBeanServer.invoke(on, "browse", new Object[]{limit, sortByLongestDuration}, new String[]{"int", "boolean"}); + Collection<CompositeData> values = (Collection<CompositeData>) list.values(); + for (CompositeData data : values) { + Map<String, Object> row = new LinkedHashMap<String, Object>(); + Object exchangeId = data.get("exchangeId"); + if (exchangeId != null) { + row.put("exchangeId", exchangeId); + } + Object routeId = data.get("routeId"); + if (routeId != null) { + row.put("routeId", routeId); + } + Object nodeId = data.get("nodeId"); + if (nodeId != null) { + row.put("nodeId", nodeId); + } + Object duration = data.get("duration"); + if (duration != null) { + row.put("duration", duration); + } + Object elapsed = data.get("elapsed"); + if (elapsed != null) { + row.put("elapsed", elapsed); + } + answer.add(row); + } + } + } + + return answer; + } + public void startContext(String camelContextName) throws Exception { CamelContext context = getLocalCamelContext(camelContextName); if (context != null) { http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java index dd07768..5ab7e8e 100644 --- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java @@ -54,6 +54,17 @@ public interface CamelController { String getCamelContextStatsAsXml(String camelContextName, boolean fullStats, boolean includeProcessors) throws Exception; /** + * Browses the inflight exchanges + * + * @param camelContextName the Camel context. + * @param limit maximum number of exchanges to return + * @param sortByLongestDuration <tt>true</tt> to sort by longest duration, <tt>false</tt> to sort by exchange id + * @return a list of key/value pairs with inflight exchange information + * @throws java.lang.Exception can be thrown + */ + List<Map<String, Object>> browseInflightExchanges(String camelContextName, int limit, boolean sortByLongestDuration) throws Exception; + + /** * Starts the given Camel context. * * @param camelContextName the Camel context. http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInflightCommand.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInflightCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInflightCommand.java new file mode 100644 index 0000000..9c24480 --- /dev/null +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInflightCommand.java @@ -0,0 +1,150 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.commands; + +import java.io.PrintStream; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +/** + * Command to display inflight exchange information + */ +public class ContextInflightCommand extends AbstractContextCommand { + + private static final String EXCHANGE_COLUMN_LABEL = "ExchangeId"; + private static final String ROUTE_COLUMN_LABEL = "Route"; + private static final String NODE_COLUMN_LABEL = "Node"; + private static final String DURATION_COLUMN_LABEL = "Duration (ms)"; + private static final String ELAPSED_COLUMN_LABEL = "Elapsed (ms)"; + + private static final int DEFAULT_COLUMN_WIDTH_INCREMENT = 0; + private static final String DEFAULT_FIELD_PREAMBLE = " "; + private static final String DEFAULT_FIELD_POSTAMBLE = " "; + private static final String DEFAULT_HEADER_PREAMBLE = " "; + private static final String DEFAULT_HEADER_POSTAMBLE = " "; + private static final int DEFAULT_FORMAT_BUFFER_LENGTH = 24; + private static final int MAX_COLUMN_WIDTH = Integer.MAX_VALUE; + private static final int MIN_COLUMN_WIDTH = 12; + + private int limit; + private boolean sortByLongestDuration; + + public ContextInflightCommand(String context, int limit, boolean sortByLongestDuration) { + super(context); + this.limit = limit; + this.sortByLongestDuration = sortByLongestDuration; + } + + @Override + protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception { + List<Map<String, Object>> inflight = camelController.browseInflightExchanges(contextName, limit, sortByLongestDuration); + + final Map<String, Integer> columnWidths = computeColumnWidths(inflight); + final String headerFormat = buildFormatString(columnWidths, true); + final String rowFormat = buildFormatString(columnWidths, false); + + if (inflight.size() > 0) { + out.println(String.format(headerFormat, EXCHANGE_COLUMN_LABEL, ROUTE_COLUMN_LABEL, NODE_COLUMN_LABEL, DURATION_COLUMN_LABEL, ELAPSED_COLUMN_LABEL)); + out.println(String.format(headerFormat, "----------", "-----", "----", "-------------", "------------")); + for (Map<String, Object> row : inflight) { + Object exchangeId = row.get("exchangeId"); + Object routeId = row.get("routeId"); + Object state = row.get("nodeId"); + Object duration = row.get("duration"); + Object elapsed = row.get("elapsed"); + out.println(String.format(rowFormat, exchangeId, routeId, state, safeNull(duration), safeNull(elapsed))); + } + } + + return null; + } + + private Map<String, Integer> computeColumnWidths(final Iterable<Map<String, Object>> inflight) throws Exception { + if (inflight == null) { + throw new IllegalArgumentException("Unable to determine column widths from null Iterable<Inflight>"); + } else { + int maxExchangeLen = 0; + int maxRouteLen = 0; + int maxNodeLen = 0; + int maxDurationLen = 0; + int maxElapsedLen = 0; + + for (Map<String, Object> row : inflight) { + final String exchangeId = safeNull(row.get("exchangeId")); + maxExchangeLen = java.lang.Math.max(maxExchangeLen, exchangeId == null ? 0 : exchangeId.length()); + + final String routeId = safeNull(row.get("routeId")); + maxRouteLen = java.lang.Math.max(maxRouteLen, routeId == null ? 0 : routeId.length()); + + final String nodeId = safeNull(row.get("nodeId")); + maxNodeLen = java.lang.Math.max(maxNodeLen, nodeId == null ? 0 : nodeId.length()); + + final String duration = safeNull(row.get("duration")); + maxDurationLen = java.lang.Math.max(maxDurationLen, duration == null ? 0 : duration.length()); + + final String elapsed = safeNull(row.get("elapsed")); + maxElapsedLen = java.lang.Math.max(maxElapsedLen, elapsed == null ? 0 : elapsed.length()); + } + + final Map<String, Integer> retval = new Hashtable<String, Integer>(5); + retval.put(EXCHANGE_COLUMN_LABEL, maxExchangeLen); + retval.put(ROUTE_COLUMN_LABEL, maxRouteLen); + retval.put(NODE_COLUMN_LABEL, maxNodeLen); + retval.put(DURATION_COLUMN_LABEL, maxDurationLen); + retval.put(ELAPSED_COLUMN_LABEL, maxElapsedLen); + + return retval; + } + } + + private static String buildFormatString(final Map<String, Integer> columnWidths, final boolean isHeader) { + final String fieldPreamble; + final String fieldPostamble; + final int columnWidthIncrement; + + if (isHeader) { + fieldPreamble = DEFAULT_HEADER_PREAMBLE; + fieldPostamble = DEFAULT_HEADER_POSTAMBLE; + } else { + fieldPreamble = DEFAULT_FIELD_PREAMBLE; + fieldPostamble = DEFAULT_FIELD_POSTAMBLE; + } + columnWidthIncrement = DEFAULT_COLUMN_WIDTH_INCREMENT; + + int exchangeLen = Math.min(columnWidths.get(EXCHANGE_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH); + int routeLen = Math.min(columnWidths.get(ROUTE_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH); + int nodeLen = Math.min(columnWidths.get(NODE_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH); + int durationLen = Math.min(columnWidths.get(DURATION_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH); + int elapsedLen = Math.min(columnWidths.get(ELAPSED_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH); + exchangeLen = Math.max(MIN_COLUMN_WIDTH, exchangeLen); + routeLen = Math.max(MIN_COLUMN_WIDTH, routeLen); + nodeLen = Math.max(MIN_COLUMN_WIDTH, nodeLen); + durationLen = Math.max(13, durationLen); + elapsedLen = Math.max(MIN_COLUMN_WIDTH, elapsedLen); + + final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH); + retval.append(fieldPreamble).append("%-").append(exchangeLen).append('.').append(exchangeLen).append('s').append(fieldPostamble).append(' '); + retval.append(fieldPreamble).append("%-").append(routeLen).append('.').append(routeLen).append('s').append(fieldPostamble).append(' '); + retval.append(fieldPreamble).append("%-").append(nodeLen).append('.').append(nodeLen).append('s').append(fieldPostamble).append(' '); + retval.append(fieldPreamble).append("%").append(durationLen).append('.').append(durationLen).append('s').append(fieldPostamble).append(' '); + retval.append(fieldPreamble).append("%").append(elapsedLen).append('.').append(elapsedLen).append('s').append(fieldPostamble).append(' '); + + return retval.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java index ee8ecfb..4a31da49 100644 --- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java @@ -228,12 +228,4 @@ public class ContextInfoCommand extends AbstractCamelCommand { } - private static String safeNull(Object value) { - if (value == null) { - return ""; - } else { - return value.toString(); - } - } - } http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java b/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java index 9d95c38..0a44998 100644 --- a/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java +++ b/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java @@ -226,6 +226,39 @@ public class DefaultJolokiaCamelController extends AbstractCamelController imple } @Override + public List<Map<String, Object>> browseInflightExchanges(String camelContextName, int limit, boolean sortByLongestDuration) throws Exception { + if (jolokia == null) { + throw new IllegalStateException("Need to connect to remote jolokia first"); + } + + List<Map<String, Object>> answer = new ArrayList<Map<String, Object>>(); + + ObjectName found = lookupCamelContext(camelContextName); + if (found != null) { + String pattern = String.format("%s:context=%s,type=services,name=\"%s\"", found.getDomain(), found.getKeyProperty("context"), "DefaultInflightRepository"); + ObjectName on = ObjectName.getInstance(pattern); + J4pExecResponse er = jolokia.execute(new J4pExecRequest(on, "browse(int,boolean)", limit, sortByLongestDuration)); + if (er != null) { + JSONObject data = er.getValue(); + for (Object obj : data.values()) { + JSONObject data2 = (JSONObject) obj; + JSONObject inflight = (JSONObject) data2.values().iterator().next(); + + Map<String, Object> row = new LinkedHashMap<String, Object>(); + row.put("exchangeId", asString(inflight.get("exchangeId"))); + row.put("routeId", asString(inflight.get("routeIdId"))); + row.put("nodeId", asString(inflight.get("nodeId"))); + row.put("duration", asString(inflight.get("duration"))); + row.put("elapsed", asString(inflight.get("elapsed"))); + answer.add(row); + } + } + } + + return answer; + } + + @Override public void startContext(String camelContextName) throws Exception { if (jolokia == null) { throw new IllegalStateException("Need to connect to remote jolokia first"); http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/CamelCommandSupport.java ---------------------------------------------------------------------- diff --git a/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/CamelCommandSupport.java b/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/CamelCommandSupport.java index d419256..59204a8 100644 --- a/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/CamelCommandSupport.java +++ b/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/CamelCommandSupport.java @@ -30,12 +30,4 @@ public abstract class CamelCommandSupport extends OsgiCommandSupport { this.camelController = camelController; } - String safeNull(String s) { - if (s == null) { - return ""; - } else { - return s; - } - } - } http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/ContextInflight.java ---------------------------------------------------------------------- diff --git a/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/ContextInflight.java b/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/ContextInflight.java new file mode 100644 index 0000000..cba5ca3 --- /dev/null +++ b/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/ContextInflight.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.karaf.commands; + +import org.apache.camel.commands.ContextInflightCommand; +import org.apache.felix.gogo.commands.Argument; +import org.apache.felix.gogo.commands.Command; +import org.apache.felix.gogo.commands.Option; + +@Command(scope = "camel", name = "context-inflight", description = "List inflight exchanges.") +public class ContextInflight extends CamelCommandSupport { + + @Argument(index = 0, name = "name", description = "The Camel context name", required = true, multiValued = false) + String name; + + @Option(name = "--limit", aliases = "-l", description = "To limit the number of exchanges shown", + required = false, multiValued = false) + int limit = -1; + + @Option(name = "--sort", aliases = "-s", description = "true = sort by longest duration, false = sort by exchange id", + required = false, multiValued = false, valueToShowInHelp = "false") + boolean sortByLongestDuration; + + protected Object doExecute() throws Exception { + ContextInflightCommand command = new ContextInflightCommand(name, limit, sortByLongestDuration); + return command.execute(camelController, System.out, System.err); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f812a45e/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml ---------------------------------------------------------------------- diff --git a/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml b/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml index ec77fb6..fc54ac8 100644 --- a/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml +++ b/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml @@ -69,6 +69,15 @@ <null/> </completers> </command> + <command name="camel/context-inflight"> + <action class="org.apache.camel.karaf.commands.ContextInflight"> + <property name="camelController" ref="camelController"/> + </action> + <completers> + <ref component-id="camelContextCompleter"/> + <null/> + </completers> + </command> <command name="camel/route-list"> <action class="org.apache.camel.karaf.commands.RouteList"> <property name="camelController" ref="camelController"/>