You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2020/03/30 07:00:46 UTC

[camel-karaf] 01/03: Moved platform commands to camel-karaf

This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-karaf.git

commit 96546def5a3c631ab8e5167fe47525611f97c69d
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Mon Mar 30 08:39:07 2020 +0200

    Moved platform commands to camel-karaf
---
 platforms/commands/commands-core/pom.xml           | 109 ++++
 .../camel/commands/AbstractCamelCommand.java       |  42 ++
 .../camel/commands/AbstractCamelController.java    |  51 ++
 .../camel/commands/AbstractContextCommand.java     |  92 +++
 .../commands/AbstractLocalCamelController.java     | 657 +++++++++++++++++++++
 .../camel/commands/AbstractRouteCommand.java       | 109 ++++
 .../org/apache/camel/commands/CamelCommand.java    |  37 ++
 .../org/apache/camel/commands/CamelController.java | 266 +++++++++
 .../camel/commands/ContextInflightCommand.java     | 162 +++++
 .../apache/camel/commands/ContextInfoCommand.java  | 245 ++++++++
 .../apache/camel/commands/ContextListCommand.java  | 145 +++++
 .../camel/commands/ContextResumeCommand.java       |  35 ++
 .../apache/camel/commands/ContextStartCommand.java |  35 ++
 .../apache/camel/commands/ContextStopCommand.java  |  35 ++
 .../camel/commands/ContextSuspendCommand.java      |  35 ++
 .../apache/camel/commands/EndpointListCommand.java | 169 ++++++
 .../camel/commands/EndpointStatisticCommand.java   | 249 ++++++++
 .../camel/commands/LocalCamelController.java       |  47 ++
 .../apache/camel/commands/RestApiDocCommand.java   |  42 ++
 .../camel/commands/RestRegistryListCommand.java    | 198 +++++++
 .../org/apache/camel/commands/RestShowCommand.java |  40 ++
 .../apache/camel/commands/RouteInfoCommand.java    | 120 ++++
 .../apache/camel/commands/RouteListCommand.java    | 167 ++++++
 .../apache/camel/commands/RouteProfileCommand.java |  85 +++
 .../camel/commands/RouteResetStatsCommand.java     |  35 ++
 .../apache/camel/commands/RouteResumeCommand.java  |  34 ++
 .../apache/camel/commands/RouteShowCommand.java    |  39 ++
 .../apache/camel/commands/RouteStartCommand.java   |  34 ++
 .../apache/camel/commands/RouteStepCommand.java    |  83 +++
 .../apache/camel/commands/RouteStopCommand.java    |  34 ++
 .../apache/camel/commands/RouteSuspendCommand.java |  34 ++
 .../org/apache/camel/commands/StringEscape.java    |  29 +
 .../camel/commands/TransformerListCommand.java     | 198 +++++++
 .../camel/commands/ValidatorListCommand.java       | 180 ++++++
 .../apache/camel/commands/internal/MatchUtil.java  |  31 +
 .../apache/camel/commands/internal/RegexUtil.java  |  69 +++
 .../src/main/resources/META-INF/LICENSE.txt        | 203 +++++++
 .../src/main/resources/META-INF/NOTICE.txt         |  11 +
 .../commands/AbstractLocalCamelControllerTest.java | 159 +++++
 .../camel/commands/ContextListCommandTest.java     |  97 +++
 .../camel/commands/DummyCamelController.java       |  51 ++
 .../camel/commands/ValidatorListCommandTest.java   | 123 ++++
 .../camel/commands/internal/RegexUtilTest.java     |  52 ++
 .../src/test/resources/log4j2.properties           |  30 +
 platforms/commands/pom.xml                         |  37 ++
 platforms/pom.xml                                  |   1 +
 46 files changed, 4736 insertions(+)

diff --git a/platforms/commands/commands-core/pom.xml b/platforms/commands/commands-core/pom.xml
new file mode 100644
index 0000000..de13c29
--- /dev/null
+++ b/platforms/commands/commands-core/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>commands</artifactId>
+        <version>3.2.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>camel-commands-core</artifactId>
+    <packaging>jar</packaging>
+    <name>Camel :: Platforms :: Commands :: Core</name>
+    <description>Core Camel Commands</description>
+
+    <properties>
+        <!-- use by camel-catalog -->
+        <firstVersion>2.15.0</firstVersion>
+        <label>tooling</label>
+
+        <camel.osgi.export.pkg>org.apache.camel.commands.*</camel.osgi.export.pkg>
+    </properties>
+
+    <dependencies>
+
+        <!-- camel -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-management</artifactId>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- logging -->
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
+    </dependencies>
+
+    <build>
+        <plugins>
+
+            <plugin>
+                <groupId>org.apache.camel</groupId>
+                <artifactId>camel-bundle-plugin</artifactId>
+                <version>${project.version}</version>
+                <configuration>
+                    <instructions>
+                        <Private-Package>
+                            org.apache.camel.commands.internal
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>bundle-manifest</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
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
new file mode 100644
index 0000000..fe58e28
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelCommand.java
@@ -0,0 +1,42 @@
+/*
+ * 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 org.apache.camel.util.ObjectHelper;
+
+/**
+ * Abstract base command for {@link org.apache.camel.commands.CamelCommand}
+ */
+public abstract class AbstractCamelCommand implements CamelCommand {
+
+    public String safeNull(String s) {
+        if (ObjectHelper.isEmpty(s)) {
+            return "";
+        } else {
+            return s;
+        }
+    }
+
+    public String safeNull(Object s) {
+        if (ObjectHelper.isEmpty(s)) {
+            return "";
+        } else {
+            return s.toString();
+        }
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelController.java
new file mode 100644
index 0000000..0e79098
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractCamelController.java
@@ -0,0 +1,51 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.commands.internal.MatchUtil;
+import org.apache.camel.commands.internal.RegexUtil;
+
+/**
+ * Abstract {@link org.apache.camel.commands.CamelController} that implementators should extend.
+ */
+public abstract class AbstractCamelController implements CamelController {
+
+    @Override
+    public List<Map<String, String>> getCamelContexts(String filter) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        List<Map<String, String>> context = getCamelContexts();
+        if (filter != null) {
+            filter = RegexUtil.wildcardAsRegex(filter);
+        } else {
+            filter = "*";
+        }
+        for (Map<String, String> entry : context) {
+            String name = entry.get("name");
+            if (name.equalsIgnoreCase(filter) || MatchUtil.matchWildcard(name, filter) || name.matches(filter)) {
+                answer.add(entry);
+            }
+        }
+
+        return answer;
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractContextCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractContextCommand.java
new file mode 100644
index 0000000..1ddd134
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractContextCommand.java
@@ -0,0 +1,92 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * Abstract command for working with a single {@link org.apache.camel.CamelContext}
+ */
+public abstract class AbstractContextCommand extends AbstractCamelCommand {
+
+    String context;
+
+    /**
+     * @param context The name of the Camel context.
+     */
+    protected AbstractContextCommand(String context) {
+        this.context = context;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        if (camelController instanceof LocalCamelController) {
+            return executeLocal((LocalCamelController) camelController, out, err);
+        } else {
+            boolean found = false;
+            List<Map<String, String>> contexts = camelController.getCamelContexts();
+            for (Map<String, String> entry : contexts) {
+                String name = entry.get("name");
+                if (context.equals(name)) {
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                err.println("Camel context " + context + " not found.");
+                return null;
+            } else {
+                return performContextCommand(camelController, context, out, err);
+            }
+        }
+    }
+
+    protected Object executeLocal(LocalCamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        CamelContext camelContext = camelController.getLocalCamelContext(context);
+        if (camelContext == null) {
+            err.println("Camel context " + context + " not found.");
+            return null;
+        }
+
+        // Setting thread context classloader to the bundle classloader to enable legacy code that relies on it
+        ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(camelContext.getApplicationContextClassLoader());
+        try {
+            return performContextCommand(camelController, camelContext.getName(), out, err);
+        } finally {
+            Thread.currentThread().setContextClassLoader(oldClassloader);
+        }
+    }
+
+    /**
+     * Perform Context-specific command
+     *
+     * @param camelController the Camel controller
+     * @param contextName     the Camel context name
+     * @param out             the output printer stream
+     * @param err             the error print stream
+     * @return response from command, or <tt>null</tt> if nothing to return
+     * @throws Exception is thrown if error executing command
+     */
+    protected abstract Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception;
+
+}
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
new file mode 100644
index 0000000..a65b02b
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java
@@ -0,0 +1,657 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+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;
+import org.apache.camel.ExtendedCamelContext;
+import org.apache.camel.Route;
+import org.apache.camel.ServiceStatus;
+import org.apache.camel.StatefulService;
+import org.apache.camel.api.management.ManagedCamelContext;
+import org.apache.camel.api.management.mbean.ManagedRouteMBean;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.rest.RestDefinition;
+import org.apache.camel.model.rest.RestsDefinition;
+import org.apache.camel.spi.EndpointRegistry;
+import org.apache.camel.spi.ManagementAgent;
+import org.apache.camel.spi.RestRegistry;
+import org.apache.camel.spi.RuntimeEndpointRegistry;
+import org.apache.camel.spi.Transformer;
+import org.apache.camel.spi.Validator;
+
+/**
+ * Abstract {@link org.apache.camel.commands.LocalCamelController} that implementators should extend when implementing
+ * a controller that runs locally in the same JVM as Camel.
+ */
+public abstract class AbstractLocalCamelController extends AbstractCamelController implements LocalCamelController {
+
+    @Override
+    public CamelContext getLocalCamelContext(String name) throws Exception {
+        for (CamelContext camelContext : this.getLocalCamelContexts()) {
+            if (camelContext.getName().equals(name)) {
+                return camelContext;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, Object> getCamelContextInformation(String name) throws Exception {
+        Map<String, Object> answer = new LinkedHashMap<>();
+        CamelContext context = getLocalCamelContext(name);
+        if (context != null) {
+            answer.put("name", context.getName());
+            answer.put("managementName", context.getManagementName());
+            answer.put("version", context.getVersion());
+            answer.put("status", context.getStatus().name());
+            answer.put("uptime", context.getUptime());
+            answer.put("suspended", context.getStatus().isSuspended());
+            if (context.getManagementStrategy().getManagementAgent() != null) {
+                String level = context.getManagementStrategy().getManagementAgent().getStatisticsLevel().name();
+                answer.put("managementStatisticsLevel", level);
+            }
+            answer.put("allowUseOriginalMessage", context.isAllowUseOriginalMessage());
+            answer.put("messageHistory", context.isMessageHistory());
+            answer.put("tracing", context.isTracing());
+            answer.put("logMask", context.isLogMask());
+            answer.put("shutdownTimeout", context.getShutdownStrategy().getTimeUnit().toSeconds(context.getShutdownStrategy().getTimeout()));
+            answer.put("classResolver", context.getClassResolver().toString());
+            answer.put("packageScanClassResolver", context.adapt(ExtendedCamelContext.class).getPackageScanClassResolver().toString());
+            answer.put("applicationContextClassLoader", context.getApplicationContextClassLoader().toString());
+            answer.put("headersMapFactory", context.adapt(ExtendedCamelContext.class).getHeadersMapFactory().toString());
+
+            for (Map.Entry<String, String> entry : context.getGlobalOptions().entrySet()) {
+                answer.put("property." + entry.getKey(), entry.getValue());
+            }
+
+            long activeRoutes = 0;
+            long inactiveRoutes = 0;
+            List<Route> routeList = context.getRoutes();
+            for (Route route : routeList) {
+                if (context.getRouteController().getRouteStatus(route.getId()).isStarted()) {
+                    activeRoutes++;
+                } else {
+                    inactiveRoutes++;
+                }
+            }
+            answer.put("startedRoutes", activeRoutes);
+            answer.put("totalRoutes", activeRoutes + inactiveRoutes);
+
+            // add type converter details
+            answer.put("typeConverter.numberOfTypeConverters", context.getTypeConverterRegistry().size());
+            answer.put("typeConverter.statisticsEnabled", context.getTypeConverterRegistry().getStatistics().isStatisticsEnabled());
+            answer.put("typeConverter.noopCounter", context.getTypeConverterRegistry().getStatistics().getNoopCounter());
+            answer.put("typeConverter.attemptCounter", context.getTypeConverterRegistry().getStatistics().getAttemptCounter());
+            answer.put("typeConverter.hitCounter", context.getTypeConverterRegistry().getStatistics().getHitCounter());
+            answer.put("typeConverter.missCounter", context.getTypeConverterRegistry().getStatistics().getMissCounter());
+            answer.put("typeConverter.failedCounter", context.getTypeConverterRegistry().getStatistics().getFailedCounter());
+
+            // add async processor await manager details
+            ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
+            answer.put("asyncProcessorAwaitManager.size", ecc.getAsyncProcessorAwaitManager().size());
+            answer.put("asyncProcessorAwaitManager.statisticsEnabled", ecc.getAsyncProcessorAwaitManager().getStatistics().isStatisticsEnabled());
+            answer.put("asyncProcessorAwaitManager.threadsBlocked", ecc.getAsyncProcessorAwaitManager().getStatistics().getThreadsBlocked());
+            answer.put("asyncProcessorAwaitManager.threadsInterrupted", ecc.getAsyncProcessorAwaitManager().getStatistics().getThreadsInterrupted());
+            answer.put("asyncProcessorAwaitManager.totalDuration", ecc.getAsyncProcessorAwaitManager().getStatistics().getTotalDuration());
+            answer.put("asyncProcessorAwaitManager.minDuration", ecc.getAsyncProcessorAwaitManager().getStatistics().getMinDuration());
+            answer.put("asyncProcessorAwaitManager.maxDuration", ecc.getAsyncProcessorAwaitManager().getStatistics().getMaxDuration());
+            answer.put("asyncProcessorAwaitManager.meanDuration", ecc.getAsyncProcessorAwaitManager().getStatistics().getMeanDuration());
+
+            // add stream caching details if enabled
+            if (context.getStreamCachingStrategy().isEnabled()) {
+                answer.put("streamCachingEnabled", true);
+                answer.put("streamCaching.spoolDirectory", context.getStreamCachingStrategy().getSpoolDirectory());
+                answer.put("streamCaching.spoolCipher", context.getStreamCachingStrategy().getSpoolCipher());
+                answer.put("streamCaching.spoolThreshold", context.getStreamCachingStrategy().getSpoolThreshold());
+                answer.put("streamCaching.spoolUsedHeapMemoryThreshold", context.getStreamCachingStrategy().getSpoolUsedHeapMemoryThreshold());
+                answer.put("streamCaching.spoolUsedHeapMemoryLimit", context.getStreamCachingStrategy().getSpoolUsedHeapMemoryLimit());
+                answer.put("streamCaching.anySpoolRules", context.getStreamCachingStrategy().isAnySpoolRules());
+                answer.put("streamCaching.bufferSize", context.getStreamCachingStrategy().getBufferSize());
+                answer.put("streamCaching.removeSpoolDirectoryWhenStopping", context.getStreamCachingStrategy().isRemoveSpoolDirectoryWhenStopping());
+                answer.put("streamCaching.statisticsEnabled", context.getStreamCachingStrategy().getStatistics().isStatisticsEnabled());
+
+                if (context.getStreamCachingStrategy().getStatistics().isStatisticsEnabled()) {
+                    answer.put("streamCaching.cacheMemoryCounter", context.getStreamCachingStrategy().getStatistics().getCacheMemoryCounter());
+                    answer.put("streamCaching.cacheMemorySize", context.getStreamCachingStrategy().getStatistics().getCacheMemorySize());
+                    answer.put("streamCaching.cacheMemoryAverageSize", context.getStreamCachingStrategy().getStatistics().getCacheMemoryAverageSize());
+                    answer.put("streamCaching.cacheSpoolCounter", context.getStreamCachingStrategy().getStatistics().getCacheSpoolCounter());
+                    answer.put("streamCaching.cacheSpoolSize", context.getStreamCachingStrategy().getStatistics().getCacheSpoolSize());
+                    answer.put("streamCaching.cacheSpoolAverageSize", context.getStreamCachingStrategy().getStatistics().getCacheSpoolAverageSize());
+                }
+            } else {
+                answer.put("streamCachingEnabled", false);
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
+    public String getCamelContextStatsAsXml(String camelContextName, boolean fullStats, boolean includeProcessors) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+
+        ManagementAgent agent = context.getManagementStrategy().getManagementAgent();
+        if (agent != null) {
+            MBeanServer mBeanServer = agent.getMBeanServer();
+            ObjectName query = ObjectName.getInstance(agent.getMBeanObjectDomainName() + ":type=context,*");
+            Set<ObjectName> set = mBeanServer.queryNames(query, null);
+            for (ObjectName contextMBean : set) {
+                String camelId = (String) mBeanServer.getAttribute(contextMBean, "CamelId");
+                if (camelId != null && camelId.equals(context.getName())) {
+                    String xml = (String) mBeanServer.invoke(contextMBean, "dumpRoutesStatsAsXml", new Object[]{fullStats, includeProcessors}, new String[]{"boolean", "boolean"});
+                    return xml;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<Map<String, Object>> browseInflightExchanges(String camelContextName, String route, int limit, boolean sortByLongestDuration) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+
+        List<Map<String, Object>> answer = new ArrayList<>();
+
+        ManagementAgent agent = context.getManagementStrategy().getManagementAgent();
+        if (agent != null) {
+            MBeanServer mBeanServer = agent.getMBeanServer();
+            ObjectName on = new ObjectName(agent.getMBeanObjectDomainName() + ":type=services,name=DefaultInflightRepository,context=" + context.getManagementName());
+            if (mBeanServer.isRegistered(on)) {
+                TabularData list = (TabularData) mBeanServer.invoke(on, "browse", new Object[]{route, limit, sortByLongestDuration}, new String[]{"java.lang.String", "int", "boolean"});
+                Collection<CompositeData> values = (Collection<CompositeData>) list.values();
+                for (CompositeData data : values) {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    Object exchangeId = data.get("exchangeId");
+                    if (exchangeId != null) {
+                        row.put("exchangeId", exchangeId);
+                    }
+                    Object fromRouteId = data.get("fromRouteId");
+                    if (fromRouteId != null) {
+                        row.put("fromRouteId", fromRouteId);
+                    }
+                    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 elapsed = data.get("elapsed");
+                    if (elapsed != null) {
+                        row.put("elapsed", elapsed);
+                    }
+                    Object duration = data.get("duration");
+                    if (duration != null) {
+                        row.put("duration", duration);
+                    }
+                    answer.add(row);
+                }
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
+    public void startContext(String camelContextName) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            if (context.getStatus().equals(ServiceStatus.Suspended)) {
+                context.resume();
+            } else {
+                context.start();
+            }
+        }
+    }
+
+    @Override
+    public void stopContext(String camelContextName) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.stop();
+        }
+    }
+
+    @Override
+    public void suspendContext(String camelContextName) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.suspend();
+        }
+    }
+
+    @Override
+    public void resumeContext(String camelContextName) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.resume();
+        }
+    }
+
+    @Override
+    public List<Map<String, String>> getRoutes(String camelContextName) throws Exception {
+        return getRoutes(camelContextName, null);
+    }
+
+    @Override
+    public List<Map<String, String>> getRoutes(String camelContextName, String filter) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null) {
+                for (Route route : context.getRoutes()) {
+                    if (filter == null || route.getId().matches(filter)) {
+                        Map<String, String> row = new LinkedHashMap<>();
+                        row.put("camelContextName", context.getName());
+                        row.put("routeId", route.getId());
+                        row.put("state", getRouteState(route));
+                        row.put("uptime", route.getUptime());
+                        ManagedCamelContext mcc = context.getExtension(ManagedCamelContext.class);
+                        if (mcc != null && mcc.getManagedCamelContext() != null) {
+                            ManagedRouteMBean mr = mcc.getManagedRoute(route.getId());
+                            row.put("exchangesTotal", "" + mr.getExchangesTotal());
+                            row.put("exchangesInflight", "" + mr.getExchangesInflight());
+                            row.put("exchangesFailed", "" + mr.getExchangesFailed());
+                        } else {
+                            row.put("exchangesTotal", "0");
+                            row.put("exchangesInflight", "0");
+                            row.put("exchangesFailed", "0");
+                        }
+                        answer.add(row);
+                    }
+                }
+            }
+        } else {
+            List<Map<String, String>> camelContexts = this.getCamelContexts();
+            for (Map<String, String> row : camelContexts) {
+                List<Map<String, String>> routes = getRoutes(row.get("name"), filter);
+                answer.addAll(routes);
+            }
+        }
+
+        // sort the list
+        Collections.sort(answer, new Comparator<Map<String, String>>() {
+            @Override
+            public int compare(Map<String, String> o1, Map<String, String> o2) {
+                // group by camel context first, then by route name
+                String c1 = o1.get("camelContextName");
+                String c2 = o2.get("camelContextName");
+
+                int answer = c1.compareTo(c2);
+                if (answer == 0) {
+                    // okay from same camel context, then sort by route id
+                    answer = o1.get("routeId").compareTo(o2.get("routeId"));
+                }
+                return answer;
+            }
+        });
+        return answer;
+    }
+
+    @Override
+    public void resetRouteStats(String camelContextName) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return;
+        }
+
+        ManagementAgent agent = context.getManagementStrategy().getManagementAgent();
+        if (agent != null) {
+            MBeanServer mBeanServer = agent.getMBeanServer();
+
+            // reset route mbeans
+            ObjectName query = ObjectName.getInstance(agent.getMBeanObjectDomainName() + ":type=routes,*");
+            Set<ObjectName> set = mBeanServer.queryNames(query, null);
+            for (ObjectName routeMBean : set) {
+                String camelId = (String) mBeanServer.getAttribute(routeMBean, "CamelId");
+                if (camelId != null && camelId.equals(context.getName())) {
+                    mBeanServer.invoke(routeMBean, "reset", new Object[]{true}, new String[]{"boolean"});
+                }
+            }
+        }
+    }
+
+    @Override
+    public void startRoute(String camelContextName, String routeId) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.getRouteController().startRoute(routeId);
+        }
+    }
+
+    @Override
+    public void stopRoute(String camelContextName, String routeId) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.getRouteController().stopRoute(routeId);
+        }
+    }
+
+    @Override
+    public void suspendRoute(String camelContextName, String routeId) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.getRouteController().suspendRoute(routeId);
+        }
+    }
+
+    @Override
+    public void resumeRoute(String camelContextName, String routeId) throws Exception {
+        CamelContext context = getLocalCamelContext(camelContextName);
+        if (context != null) {
+            context.getRouteController().resumeRoute(routeId);
+        }
+    }
+
+    @Override
+    public String getRouteModelAsXml(String routeId, String camelContextName) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+        RouteDefinition route = context.getExtension(Model.class).getRouteDefinition(routeId);
+        if (route == null) {
+            return null;
+        }
+
+        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
+        return ecc.getModelToXMLDumper().dumpModelAsXml(context, route);
+    }
+
+    @Override
+    public String getRouteStatsAsXml(String routeId, String camelContextName, boolean fullStats, boolean includeProcessors) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+
+        ManagementAgent agent = context.getManagementStrategy().getManagementAgent();
+        if (agent != null) {
+            MBeanServer mBeanServer = agent.getMBeanServer();
+            Set<ObjectName> set = mBeanServer.queryNames(new ObjectName(agent.getMBeanObjectDomainName() + ":type=routes,name=\"" + routeId + "\",*"), null);            
+            for (ObjectName routeMBean : set) {
+
+                // the route must be part of the camel context
+                String camelId = (String) mBeanServer.getAttribute(routeMBean, "CamelId");
+                if (camelId != null && camelId.equals(camelContextName)) {
+                    String xml = (String) mBeanServer.invoke(routeMBean, "dumpRouteStatsAsXml", new Object[]{fullStats, includeProcessors}, new String[]{"boolean", "boolean"});
+                    return xml;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getStepStatsAsXml(String routeId, String camelContextName, boolean fullStats) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+
+        ManagementAgent agent = context.getManagementStrategy().getManagementAgent();
+        if (agent != null) {
+            MBeanServer mBeanServer = agent.getMBeanServer();
+            Set<ObjectName> set = mBeanServer.queryNames(new ObjectName(agent.getMBeanObjectDomainName() + ":type=routes,name=\"" + routeId + "\",*"), null);
+            for (ObjectName routeMBean : set) {
+
+                // the route must be part of the camel context
+                String camelId = (String) mBeanServer.getAttribute(routeMBean, "CamelId");
+                if (camelId != null && camelId.equals(camelContextName)) {
+                    String xml = (String) mBeanServer.invoke(routeMBean, "dumpStepStatsAsXml", new Object[]{fullStats}, new String[]{"boolean"});
+                    return xml;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getRestModelAsXml(String camelContextName) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+
+        List<RestDefinition> rests = context.getExtension(Model.class).getRestDefinitions();
+        if (rests == null || rests.isEmpty()) {
+            return null;
+        }
+        // use a rests definition to dump the rests
+        RestsDefinition def = new RestsDefinition();
+        def.setRests(rests);
+
+        ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
+        return ecc.getModelToXMLDumper().dumpModelAsXml(context, def);
+    }
+
+    @Override
+    public String getRestApiDocAsJson(String camelContextName) throws Exception {
+        CamelContext context = this.getLocalCamelContext(camelContextName);
+        if (context == null) {
+            return null;
+        }
+
+        return context.getRestRegistry().apiDocAsJson();
+    }
+
+    @Override
+    public List<Map<String, String>> getEndpoints(String camelContextName) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null) {
+                List<Endpoint> endpoints = new ArrayList<>(context.getEndpoints());
+                // sort routes
+                Collections.sort(endpoints, new Comparator<Endpoint>() {
+                    @Override
+                    public int compare(Endpoint o1, Endpoint o2) {
+                        return o1.getEndpointKey().compareTo(o2.getEndpointKey());
+                    }
+                });
+                for (Endpoint endpoint : endpoints) {
+                    Map<String, String> row = new LinkedHashMap<>();
+                    row.put("camelContextName", context.getName());
+                    row.put("uri", endpoint.getEndpointUri());
+                    row.put("state", getEndpointState(endpoint));
+                    answer.add(row);
+                }
+            }
+        }
+        return answer;
+    }
+
+    @Override
+    public List<Map<String, String>> getEndpointRuntimeStatistics(String camelContextName) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null && context.getRuntimeEndpointRegistry() != null) {
+                EndpointRegistry staticRegistry = context.getEndpointRegistry();
+                for (RuntimeEndpointRegistry.Statistic stat : context.getRuntimeEndpointRegistry().getEndpointStatistics()) {
+
+                    String url = stat.getUri();
+                    String routeId = stat.getRouteId();
+                    String direction = stat.getDirection();
+                    boolean isStatic = staticRegistry.isStatic(url);
+                    boolean isDynamic = staticRegistry.isDynamic(url);
+                    long hits = stat.getHits();
+
+                    Map<String, String> row = new LinkedHashMap<>();
+                    row.put("camelContextName", context.getName());
+                    row.put("uri", url);
+                    row.put("routeId", routeId);
+                    row.put("direction", direction);
+                    row.put("static", Boolean.toString(isStatic));
+                    row.put("dynamic", Boolean.toString(isDynamic));
+                    row.put("hits", "" + hits);
+                    answer.add(row);
+                }
+            }
+
+            // sort the list
+            Collections.sort(answer, new Comparator<Map<String, String>>() {
+                @Override
+                public int compare(Map<String, String> endpoint1, Map<String, String> endpoint2) {
+                    // sort by route id
+                    String route1 = endpoint1.get("routeId");
+                    String route2 = endpoint2.get("routeId");
+                    int num = route1.compareTo(route2);
+                    if (num == 0) {
+                        // we want in before out
+                        String dir1 = endpoint1.get("direction");
+                        String dir2 = endpoint2.get("direction");
+                        num = dir1.compareTo(dir2);
+                    }
+                    return num;
+                }
+
+            });
+        }
+        return answer;
+    }
+
+    @Override
+    public List<Map<String, String>> getRestServices(String camelContextName) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null) {
+                List<RestRegistry.RestService> services = new ArrayList<>(context.getRestRegistry().listAllRestServices());
+                Collections.sort(services, new Comparator<RestRegistry.RestService>() {
+                    @Override
+                    public int compare(RestRegistry.RestService o1, RestRegistry.RestService o2) {
+                        return o1.getUrl().compareTo(o2.getUrl());
+                    }
+                });
+                for (RestRegistry.RestService service : services) {
+                    Map<String, String> row = new LinkedHashMap<>();
+                    row.put("basePath", service.getBasePath());
+                    row.put("baseUrl", service.getBaseUrl());
+                    row.put("consumes", service.getConsumes());
+                    row.put("description", service.getDescription());
+                    row.put("inType", service.getInType());
+                    row.put("method", service.getMethod());
+                    row.put("outType", service.getOutType());
+                    row.put("produces", service.getProduces());
+                    row.put("routeId", service.getRouteId());
+                    row.put("state", service.getState());
+                    row.put("uriTemplate", service.getUriTemplate());
+                    row.put("url", service.getUrl());
+                    answer.add(row);
+                }
+            }
+        }
+        return answer;
+    }
+
+    @Override
+    public List<Map<String, String>> getTransformers(String camelContextName) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null) {
+                List<Transformer> transformers = new ArrayList<>(context.getTransformerRegistry().values());
+                for (Transformer transformer : transformers) {
+                    Map<String, String> row = new LinkedHashMap<>();
+                    row.put("camelContextName", context.getName());
+                    row.put("scheme", transformer.getModel());
+                    row.put("from", transformer.getFrom().toString());
+                    row.put("to", transformer.getTo().toString());
+                    row.put("state", transformer.getStatus().toString());
+                    row.put("description", transformer.toString());
+                    answer.add(row);
+                }
+            }
+        }
+        return answer;
+    }
+
+    @Override
+    public List<Map<String, String>> getValidators(String camelContextName) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null) {
+                List<Validator> validators = new ArrayList<>(context.getValidatorRegistry().values());
+                for (Validator validator : validators) {
+                    Map<String, String> row = new LinkedHashMap<>();
+                    row.put("camelContextName", context.getName());
+                    row.put("type", validator.getType().toString());
+                    row.put("state", validator.getStatus().toString());
+                    row.put("description", validator.toString());
+                    answer.add(row);
+                }
+            }
+        }
+        return answer;
+    }
+
+    private static String getEndpointState(Endpoint endpoint) {
+        // must use String type to be sure remote JMX can read the attribute without requiring Camel classes.
+        if (endpoint instanceof StatefulService) {
+            ServiceStatus status = ((StatefulService) endpoint).getStatus();
+            return status.name();
+        }
+
+        // assume started if not a ServiceSupport instance
+        return ServiceStatus.Started.name();
+    }
+
+    private static String getRouteState(Route route) {
+        // must use String type to be sure remote JMX can read the attribute without requiring Camel classes.
+
+        ServiceStatus status = route.getCamelContext().getRouteController().getRouteStatus(route.getId());
+        if (status != null) {
+            return status.name();
+        }
+
+        // assume started if not a ServiceSupport instance
+        return ServiceStatus.Started.name();
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractRouteCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractRouteCommand.java
new file mode 100644
index 0000000..a3be7d7
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractRouteCommand.java
@@ -0,0 +1,109 @@
+/*
+ * 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.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.commands.internal.RegexUtil;
+
+/**
+ * Abstract command for working with a one ore more routes.
+ */
+public abstract class AbstractRouteCommand extends AbstractCamelCommand {
+
+    private String route;
+    private String context;
+
+    /**
+     * @param route The Camel route ID or a wildcard expression
+     * @param context The name of the Camel context.
+     */
+    protected AbstractRouteCommand(String route, String context) {
+        this.route = route;
+        this.context = context;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        List<Map<String, String>> camelRoutes = camelController.getRoutes(context, RegexUtil.wildcardAsRegex(route));
+        if (camelRoutes == null || camelRoutes.isEmpty()) {
+            err.println("Camel routes using " + route + " not found.");
+            return null;
+        }
+        // we want the routes sorted
+        Collections.sort(camelRoutes, new RouteComparator());
+
+        for (Map<String, String> row : camelRoutes) {
+            String camelContextName = row.get("camelContextName");
+            String routeId = row.get("routeId");
+            if (camelController instanceof LocalCamelController) {
+                executeLocal((LocalCamelController) camelController, camelContextName, routeId, out, err);
+            } else {
+                executeOnRoute(camelController, camelContextName, routeId, out, err);
+            }
+        }
+
+        return null;
+    }
+
+    private void executeLocal(LocalCamelController camelController, String camelContextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        CamelContext camelContext = camelController.getLocalCamelContext(context);
+        if (camelContext == null) {
+            err.println("Camel context " + context + " not found.");
+            return;
+        }
+
+        // Setting thread context classloader to the bundle classloader to enable legacy code that relies on it
+        ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader();
+        ClassLoader applicationContextClassLoader = camelContext.getApplicationContextClassLoader();
+        if (applicationContextClassLoader  != null) {
+            Thread.currentThread().setContextClassLoader(applicationContextClassLoader);
+        }
+        try {
+            executeOnRoute(camelController, camelContextName, routeId, out, err);
+        } finally {
+            Thread.currentThread().setContextClassLoader(oldClassloader);
+        }
+    }
+
+    public abstract void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception;
+
+    /**
+     * To sort the routes.
+     */
+    private static final class RouteComparator implements Comparator<Map<String, String>> {
+
+        @Override
+        public int compare(Map<String, String> route1, Map<String, String> route2) {
+            // sort by camel context first
+            String camel1 = route1.get("camelContextName");
+            String camel2 = route2.get("camelContextName");
+
+            if (camel1.equals(camel2)) {
+                return route1.get("routeId").compareTo(route2.get("routeId"));
+            } else {
+                return camel1.compareTo(camel2);
+            }
+        }
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelCommand.java
new file mode 100644
index 0000000..f42b7b4
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelCommand.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * A Camel command.
+ */
+public interface CamelCommand {
+
+    /**
+     * Executes the given command.
+     *
+     * @param camelController the Camel controller to access Camel information
+     * @param out             the output printer stream
+     * @param err             the error print stream
+     * @return response from command, or <tt>null</tt> if nothing to return
+     * @throws Exception is thrown if error executing command
+     */
+    Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception;
+
+}
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
new file mode 100644
index 0000000..c3e6482
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java
@@ -0,0 +1,266 @@
+/*
+ * 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.util.List;
+import java.util.Map;
+
+/**
+ * CamelController interface defines the expected behaviors to manipulate Camel resources (context, route, etc).
+ */
+public interface CamelController {
+
+    /**
+     * Gets information about a given Camel context by the given name.
+     *
+     * @param name the Camel context name.
+     * @return a list of key/value pairs with CamelContext information
+     * @throws java.lang.Exception can be thrown
+     */
+    Map<String, Object> getCamelContextInformation(String name) throws Exception;
+
+    /**
+     * Get the list of Camel context.
+     *
+     * @return a list of key/value pairs with CamelContext information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getCamelContexts() throws Exception;
+
+    /**
+     * Get the list of Camel context filter by reg ex.
+     *
+     * @param filter           the filter which supports * and ? as wildcards
+     * @return a list of key/value pairs with CamelContext information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getCamelContexts(String filter) throws Exception;
+
+    /**
+     * Returns detailed CamelContext and route statistics as XML identified by a ID and a Camel context.
+     *
+     * @param camelContextName  the Camel context.
+     * @param fullStats         whether to include verbose stats
+     * @param includeProcessors whether to embed per processor stats from the route
+     * @return the CamelContext statistics as XML
+     * @throws java.lang.Exception can be thrown
+     */
+    String getCamelContextStatsAsXml(String camelContextName, boolean fullStats, boolean includeProcessors) throws Exception;
+
+    /**
+     * Browses the inflight exchanges
+     *
+     * @param camelContextName        the Camel context.
+     * @param route                   the Camel route ID
+     * @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, String route, int limit, boolean sortByLongestDuration) throws Exception;
+
+    /**
+     * Starts the given Camel context.
+     *
+     * @param camelContextName the Camel context.
+     * @throws java.lang.Exception can be thrown
+     */
+    void startContext(String camelContextName) throws Exception;
+
+    /**
+     * Stops the given Camel context.
+     *
+     * @param camelContextName the Camel context.
+     * @throws java.lang.Exception can be thrown
+     */
+    void stopContext(String camelContextName) throws Exception;
+
+    /**
+     * Suspends the given Camel context.
+     *
+     * @param camelContextName the Camel context.
+     * @throws java.lang.Exception can be thrown
+     */
+    void suspendContext(String camelContextName) throws Exception;
+
+    /**
+     * Resumes the given Camel context.
+     *
+     * @param camelContextName the Camel context.
+     * @throws java.lang.Exception can be thrown
+     */
+    void resumeContext(String camelContextName) throws Exception;
+
+    /**
+     * Get all routes. If Camel context name is null, all routes from all contexts are listed.
+     *
+     * @param camelContextName the Camel context name. If null, all contexts are considered.
+     * @return a list of key/value pairs with routes information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getRoutes(String camelContextName) throws Exception;
+
+    /**
+     * Get all routes filtered by the regex.
+     *
+     * @param camelContextName the Camel context name. If null, all contexts are considered.
+     * @param filter           the filter which supports * and ? as wildcards
+     * @return a list of key/value pairs with routes information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getRoutes(String camelContextName, String filter) throws Exception;
+
+    /**
+     * Reset all the route stats for the given Camel context
+     *
+     * @param camelContextName the Camel context.
+     * @throws java.lang.Exception can be thrown
+     */
+    void resetRouteStats(String camelContextName) throws Exception;
+
+    /**
+     * Starts the given route
+     *
+     * @param camelContextName the Camel context.
+     * @param routeId          the route ID.
+     * @throws java.lang.Exception can be thrown
+     */
+    void startRoute(String camelContextName, String routeId) throws Exception;
+
+    /**
+     * Stops the given route
+     *
+     * @param camelContextName the Camel context.
+     * @param routeId          the route ID.
+     * @throws java.lang.Exception can be thrown
+     */
+    void stopRoute(String camelContextName, String routeId) throws Exception;
+
+    /**
+     * Suspends the given route
+     *
+     * @param camelContextName the Camel context.
+     * @param routeId          the route ID.
+     * @throws java.lang.Exception can be thrown
+     */
+    void suspendRoute(String camelContextName, String routeId) throws Exception;
+
+    /**
+     * Resumes the given route
+     *
+     * @param camelContextName the Camel context.
+     * @param routeId          the route ID.
+     * @throws java.lang.Exception can be thrown
+     */
+    void resumeRoute(String camelContextName, String routeId) throws Exception;
+
+    /**
+     * Return the definition of a route as XML identified by a ID and a Camel context.
+     *
+     * @param routeId          the route ID.
+     * @param camelContextName the Camel context.
+     * @return the route model as XML
+     * @throws java.lang.Exception can be thrown
+     */
+    String getRouteModelAsXml(String routeId, String camelContextName) throws Exception;
+
+    /**
+     * Returns detailed route statistics as XML identified by a ID and a Camel context.
+     *
+     * @param routeId           the route ID.
+     * @param camelContextName  the Camel context.
+     * @param fullStats         whether to include verbose stats
+     * @param includeProcessors whether to embed per processor stats from the route
+     * @return the route statistics as XML
+     * @throws java.lang.Exception can be thrown
+     */
+    String getRouteStatsAsXml(String routeId, String camelContextName, boolean fullStats, boolean includeProcessors) throws Exception;
+
+    /**
+     * Returns detailed step statistics as XML identified by a ID and a Camel context.
+     *
+     * @param routeId           the route ID.
+     * @param camelContextName  the Camel context.
+     * @param fullStats         whether to include verbose stats
+     * @return the step statistics as XML
+     * @throws java.lang.Exception can be thrown
+     */
+    String getStepStatsAsXml(String routeId, String camelContextName, boolean fullStats) throws Exception;
+
+    /**
+     * Return the endpoints
+     *
+     * @param camelContextName the Camel context.
+     * @return a list of key/value pairs with endpoint information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getEndpoints(String camelContextName) throws Exception;
+
+    /**
+     * Return endpoint runtime statistics
+     *
+     * @param camelContextName the Camel context
+     * @return a list of key/value pairs with endpoint runtime statistics
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getEndpointRuntimeStatistics(String camelContextName) throws Exception;
+
+    /**
+     * Return the definition of the REST services as XML for the given Camel context.
+     *
+     * @param camelContextName the Camel context.
+     * @return the REST model as xml
+     * @throws java.lang.Exception can be thrown
+     */
+    String getRestModelAsXml(String camelContextName) throws Exception;
+
+    /**
+     * Return the REST services API documentation as JSon (requires camel-swagger-java on classpath)
+     *
+     * @param camelContextName the Camel context.
+     * @return the REST API documentation as JSon
+     * @throws java.lang.Exception can be thrown
+     */
+    String getRestApiDocAsJson(String camelContextName) throws Exception;
+
+    /**
+     * Return the REST services for the given Camel context.
+     *
+     * @param camelContextName the Camel context.
+     * @return a list of key/value pairs with REST information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getRestServices(String camelContextName) throws Exception;
+
+    /**
+     * Return the transformers
+     *
+     * @param camelContextName the Camel context.
+     * @return a list of key/value pairs with transformer information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getTransformers(String camelContextName) throws Exception;
+
+    /**
+     * Return the validators
+     *
+     * @param camelContextName the Camel context.
+     * @return a list of key/value pairs with validator information
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getValidators(String camelContextName) throws Exception;
+}
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..665f398
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInflightCommand.java
@@ -0,0 +1,162 @@
+/*
+ * 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 FROM_ROUTE_COLUMN_LABEL = "From Route";
+    private static final String ROUTE_COLUMN_LABEL = "Route";
+    private static final String NODE_COLUMN_LABEL = "Node";
+    private static final String ELAPSED_COLUMN_LABEL = "Elapsed (ms)";
+    private static final String DURATION_COLUMN_LABEL = "Duration (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 String route;
+    private boolean sortByLongestDuration;
+
+    public ContextInflightCommand(String context, String route, int limit, boolean sortByLongestDuration) {
+        super(context);
+        this.route = route;
+        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, route, 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, FROM_ROUTE_COLUMN_LABEL, ROUTE_COLUMN_LABEL, NODE_COLUMN_LABEL, ELAPSED_COLUMN_LABEL, DURATION_COLUMN_LABEL));
+            out.println(String.format(headerFormat, "----------", "----------", "-----", "----", "------------", "-------------"));
+            for (Map<String, Object> row : inflight) {
+                Object exchangeId = row.get("exchangeId");
+                Object fromRouteId = row.get("fromRouteId");
+                Object routeId = row.get("routeId");
+                Object nodeId = row.get("nodeId");
+                Object elapsed = row.get("elapsed");
+                Object duration = row.get("duration");
+                out.println(String.format(rowFormat, exchangeId, fromRouteId, routeId, nodeId, safeNull(elapsed), safeNull(duration)));
+            }
+        }
+
+        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 maxFromRouteLen = 0;
+            int maxRouteLen = 0;
+            int maxNodeLen = 0;
+            int maxElapsedLen = 0;
+            int maxDurationLen = 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 fromRouteId = safeNull(row.get("fromRouteId"));
+                maxFromRouteLen = java.lang.Math.max(maxFromRouteLen, fromRouteId == null ? 0 : fromRouteId.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 elapsed = safeNull(row.get("elapsed"));
+                maxElapsedLen = java.lang.Math.max(maxElapsedLen, elapsed == null ? 0 : elapsed.length());
+
+                final String duration = safeNull(row.get("duration"));
+                maxDurationLen = java.lang.Math.max(maxDurationLen, duration == null ? 0 : duration.length());
+            }
+
+            final Map<String, Integer> retval = new Hashtable<>(5);
+            retval.put(EXCHANGE_COLUMN_LABEL, maxExchangeLen);
+            retval.put(FROM_ROUTE_COLUMN_LABEL, maxFromRouteLen);
+            retval.put(ROUTE_COLUMN_LABEL, maxRouteLen);
+            retval.put(NODE_COLUMN_LABEL, maxNodeLen);
+            retval.put(ELAPSED_COLUMN_LABEL, maxElapsedLen);
+            retval.put(DURATION_COLUMN_LABEL, maxDurationLen);
+
+            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 fromRouteLen = Math.min(columnWidths.get(FROM_ROUTE_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 elapsedLen = Math.min(columnWidths.get(ELAPSED_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int durationLen = Math.min(columnWidths.get(DURATION_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        exchangeLen = Math.max(MIN_COLUMN_WIDTH, exchangeLen);
+        fromRouteLen = Math.max(MIN_COLUMN_WIDTH, fromRouteLen);
+        routeLen = Math.max(MIN_COLUMN_WIDTH, routeLen);
+        nodeLen = Math.max(MIN_COLUMN_WIDTH, nodeLen);
+        elapsedLen = Math.max(MIN_COLUMN_WIDTH, elapsedLen);
+        durationLen = Math.max(13, durationLen);
+
+        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(fromRouteLen).append('.').append(fromRouteLen).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(elapsedLen).append('.').append(elapsedLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(durationLen).append('.').append(durationLen).append('s').append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+}
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
new file mode 100644
index 0000000..6c5ed2e
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java
@@ -0,0 +1,245 @@
+/*
+ * 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.io.StringReader;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.camel.support.dump.CamelContextStatDump;
+
+import static org.apache.camel.util.ObjectHelper.isEmpty;
+
+/**
+ * Command to display detailed information about a given {@link org.apache.camel.CamelContext}.
+ */
+public class ContextInfoCommand extends AbstractContextCommand {
+
+    public static final String XML_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+    public static final String OUTPUT_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    private StringEscape stringEscape;
+    private boolean verbose;
+
+    /**
+     * @param context The name of the Camel context
+     * @param verbose Whether to output verbose
+     */
+    public ContextInfoCommand(String context, boolean verbose) {
+        super(context);
+        this.verbose = verbose;
+    }
+
+    /**
+     * Sets the {@link org.apache.camel.commands.StringEscape} to use.
+     */
+    public void setStringEscape(StringEscape stringEscape) {
+        this.stringEscape = stringEscape;
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        Map<String, Object> row = camelController.getCamelContextInformation(context);
+        if (row == null || row.isEmpty()) {
+            err.println("Camel context " + context + " not found.");
+            return null;
+        }
+
+        out.println("");
+        out.println(stringEscape.unescapeJava("\u001B[1mCamel Context " + context + "\u001B[0m"));
+        out.println(stringEscape.unescapeJava("\tName: " + row.get("name")));
+        out.println(stringEscape.unescapeJava("\tManagementName: " + row.get("managementName")));
+        out.println(stringEscape.unescapeJava("\tVersion: " + row.get("version")));
+        out.println(stringEscape.unescapeJava("\tStatus: " + row.get("status")));
+        out.println(stringEscape.unescapeJava("\tUptime: " + row.get("uptime")));
+
+        out.println("");
+        out.println(stringEscape.unescapeJava("\u001B[1mMiscellaneous\u001B[0m"));
+        out.println(stringEscape.unescapeJava("\tSuspended: " + row.get("suspended")));
+        out.println(stringEscape.unescapeJava("\tShutdown Timeout: " + row.get("shutdownTimeout") + " sec."));
+        if (row.get("managementStatisticsLevel") != null) {
+            out.println(stringEscape.unescapeJava("\tManagement StatisticsLevel: " + row.get("managementStatisticsLevel")));
+        }
+        out.println(stringEscape.unescapeJava("\tAllow UseOriginalMessage: " + row.get("allowUseOriginalMessage")));
+        out.println(stringEscape.unescapeJava("\tMessage History: " + row.get("messageHistory")));
+        out.println(stringEscape.unescapeJava("\tTracing: " + row.get("tracing")));
+        out.println(stringEscape.unescapeJava("\tLog Mask: " + row.get("logMask")));
+        out.println("");
+        out.println(stringEscape.unescapeJava("\u001B[1mProperties\u001B[0m"));
+        for (Map.Entry<String, Object> entry : row.entrySet()) {
+            String key = entry.getKey();
+            if (key.startsWith("property.")) {
+                key = key.substring(9);
+                out.println(stringEscape.unescapeJava("\t" + key + " = " + entry.getValue()));
+            }
+        }
+
+        if (verbose) {
+            out.println("");
+            out.println(stringEscape.unescapeJava("\u001B[1mAdvanced\u001B[0m"));
+            out.println(stringEscape.unescapeJava("\tClassResolver: " + row.get("classResolver")));
+            out.println(stringEscape.unescapeJava("\tPackageScanClassResolver: " + row.get("packageScanClassResolver")));
+            out.println(stringEscape.unescapeJava("\tApplicationContextClassLoader: " + row.get("applicationContextClassLoader")));
+            out.println(stringEscape.unescapeJava("\tHeadersMapFactory: " + row.get("headersMapFactory")));
+
+            printStatistics(camelController, out);
+
+            // add type converter details
+            out.println(stringEscape.unescapeJava("\tNumber of type converters: " + row.get("typeConverter.numberOfTypeConverters")));
+            boolean enabled = false;
+            if (row.get("typeConverter.statisticsEnabled") != null) {
+                enabled = (boolean) row.get("typeConverter.statisticsEnabled");
+            }
+            if (enabled) {
+                long noop = (long) row.get("typeConverter.noopCounter");
+                long attempt = (long) row.get("typeConverter.attemptCounter");
+                long hit = (long) row.get("typeConverter.hitCounter");
+                long miss = (long) row.get("typeConverter.missCounter");
+                long failed = (long) row.get("typeConverter.failedCounter");
+                out.println(stringEscape.unescapeJava(String.format("\tType converter usage: [noop=%s, attempts=%s, hits=%s, misses=%s, failures=%s]", noop, attempt, hit, miss, failed)));
+            }
+
+            // add async processor await details
+            out.println(stringEscape.unescapeJava("\tNumber of blocked threads: " + row.get("asyncProcessorAwaitManager.size")));
+            enabled = false;
+            if (row.get("asyncProcessorAwaitManager.statisticsEnabled") != null) {
+                enabled = (boolean) row.get("asyncProcessorAwaitManager.statisticsEnabled");
+            }
+            if (enabled) {
+                long blocked = (long) row.get("asyncProcessorAwaitManager.threadsBlocked");
+                long interrupted = (long) row.get("asyncProcessorAwaitManager.threadsInterrupted");
+                long total = (long) row.get("asyncProcessorAwaitManager.totalDuration");
+                long min = (long) row.get("asyncProcessorAwaitManager.minDuration");
+                long max = (long) row.get("asyncProcessorAwaitManager.maxDuration");
+                long mean = (long) row.get("asyncProcessorAwaitManager.meanDuration");
+                out.println(stringEscape.unescapeJava(String.format("\tAsyncProcessorAwaitManager usage: [blocked=%s, interrupted=%s, total=%s msec, min=%s msec, max=%s msec, mean=%s msec]",
+                        blocked, interrupted, total, min, max, mean)));
+            }
+
+            // add stream caching details if enabled
+            enabled = (boolean) row.get("streamCachingEnabled");
+            if (enabled) {
+                Object spoolDirectory = safeNull(row.get("streamCaching.spoolDirectory"));
+                Object spoolCipher = safeNull(row.get("streamCaching.spoolCipher"));
+                Object spoolThreshold = safeNull(row.get("streamCaching.spoolThreshold"));
+                Object spoolUsedHeapMemoryThreshold = safeNull(row.get("streamCaching.spoolUsedHeapMemoryThreshold"));
+                Object spoolUsedHeapMemoryLimit = safeNull(row.get("streamCaching.spoolUsedHeapMemoryLimit"));
+                Object anySpoolRules = safeNull(row.get("streamCaching.anySpoolRules"));
+                Object bufferSize = safeNull(row.get("streamCaching.bufferSize"));
+                Object removeSpoolDirectoryWhenStopping = safeNull(row.get("streamCaching.removeSpoolDirectoryWhenStopping"));
+                boolean statisticsEnabled = (boolean) row.get("streamCaching.statisticsEnabled");
+
+                String text = String.format("\tStream caching: [spoolDirectory=%s, spoolCipher=%s, spoolThreshold=%s, spoolUsedHeapMemoryThreshold=%s, "
+                                + "spoolUsedHeapMemoryLimit=%s, anySpoolRules=%s, bufferSize=%s, removeSpoolDirectoryWhenStopping=%s, statisticsEnabled=%s]",
+                        spoolDirectory, spoolCipher, spoolThreshold, spoolUsedHeapMemoryThreshold, spoolUsedHeapMemoryLimit, anySpoolRules, bufferSize,
+                        removeSpoolDirectoryWhenStopping, statisticsEnabled);
+                out.println(stringEscape.unescapeJava(text));
+
+                if (statisticsEnabled) {
+                    Object cacheMemoryCounter = safeNull(row.get("streamCaching.cacheMemoryCounter"));
+                    Object cacheMemorySize = safeNull(row.get("streamCaching.cacheMemorySize"));
+                    Object cacheMemoryAverageSize = safeNull(row.get("streamCaching.cacheMemoryAverageSize"));
+                    Object cacheSpoolCounter = safeNull(row.get("streamCaching.cacheSpoolCounter"));
+                    Object cacheSpoolSize = safeNull(row.get("streamCaching.cacheSpoolSize"));
+                    Object cacheSpoolAverageSize = safeNull(row.get("streamCaching.cacheSpoolAverageSize"));
+
+                    text = String.format("\t                       [cacheMemoryCounter=%s, cacheMemorySize=%s, cacheMemoryAverageSize=%s, cacheSpoolCounter=%s, "
+                            + "cacheSpoolSize=%s, cacheSpoolAverageSize=%s]",
+                            cacheMemoryCounter, cacheMemorySize, cacheMemoryAverageSize, cacheSpoolCounter, cacheSpoolSize, cacheSpoolAverageSize);
+                    out.println(stringEscape.unescapeJava(text));
+                }
+            }
+
+            long totalRoutes = (long) row.get("totalRoutes");
+            long startedRoutes = (long) row.get("totalRoutes");
+            out.println(stringEscape.unescapeJava("\tNumber of running routes: " + startedRoutes));
+            out.println(stringEscape.unescapeJava("\tNumber of not running routes: " + (totalRoutes - startedRoutes)));
+        }
+
+        return null;
+    }
+
+    protected void printStatistics(CamelController camelController, PrintStream out) throws Exception {
+        out.println("");
+        out.println(stringEscape.unescapeJava("\u001B[1mStatistics\u001B[0m"));
+
+        String xml = camelController.getCamelContextStatsAsXml(context, true, false);
+        if (xml != null) {
+            JAXBContext context = JAXBContext.newInstance(CamelContextStatDump.class);
+            Unmarshaller unmarshaller = context.createUnmarshaller();
+
+            CamelContextStatDump stat = (CamelContextStatDump) unmarshaller.unmarshal(new StringReader(xml));
+
+            long total = stat.getExchangesCompleted() + stat.getExchangesFailed();
+            out.println(stringEscape.unescapeJava("\tExchanges Total: " + total));
+            out.println(stringEscape.unescapeJava("\tExchanges Completed: " + stat.getExchangesCompleted()));
+            out.println(stringEscape.unescapeJava("\tExchanges Failed: " + stat.getExchangesFailed()));
+            out.println(stringEscape.unescapeJava("\tExchanges Inflight: " + stat.getExchangesInflight()));
+            out.println(stringEscape.unescapeJava("\tMin Processing Time: " + stat.getMinProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tMax Processing Time: " + stat.getMaxProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tMean Processing Time: " + stat.getMeanProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tTotal Processing Time: " + stat.getTotalProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tLast Processing Time: " + stat.getLastProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tDelta Processing Time: " + stat.getDeltaProcessingTime() + " ms"));
+
+            if (isEmpty(stat.getStartTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tStart Statistics Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(stat.getStartTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tStart Statistics Date: " + text));
+            }
+
+            // Test for null to see if a any exchanges have been processed first to avoid NPE
+            if (isEmpty(stat.getResetTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tReset Statistics Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(stat.getResetTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tReset Statistics Date: " + text));
+            }
+
+            // Test for null to see if a any exchanges have been processed first to avoid NPE
+            if (isEmpty(stat.getFirstExchangeCompletedTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tFirst Exchange Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(stat.getFirstExchangeCompletedTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tFirst Exchange Date: " + text));
+            }
+
+            // Test for null to see if a any exchanges have been processed first to avoid NPE
+            if (isEmpty(stat.getLastExchangeCompletedTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tLast Exchange Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(stat.getLastExchangeCompletedTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tLast Exchange Date: " + text));
+            }
+        }
+
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextListCommand.java
new file mode 100644
index 0000000..59b536b
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextListCommand.java
@@ -0,0 +1,145 @@
+/*
+ * 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 list all {@link org.apache.camel.CamelContext} in the JVM.
+ */
+public class ContextListCommand extends AbstractCamelCommand {
+
+    private static final String CONTEXT_COLUMN_LABEL = "Context";
+    private static final String STATUS_COLUMN_LABEL = "Status";
+    private static final String TOTAL_COLUMN_LABEL = "Total #";
+    private static final String FAILED_COLUMN_LABEL = "Failed #";
+    private static final String INFLIGHT_COLUMN_LABEL = "Inflight #";
+    private static final String UPTIME_COLUMN_LABEL = "Uptime";
+    private static final int DEFAULT_FORMAT_BUFFER_LENGTH = 24;
+    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_COLUMN_WIDTH_INCREMENT = 0;
+    private static final int MAX_COLUMN_WIDTH = Integer.MAX_VALUE;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        final List<Map<String, String>> camelContexts = camelController.getCamelContexts();
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(camelContexts);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        if (camelContexts.size() > 0) {
+            out.println(String.format(headerFormat, CONTEXT_COLUMN_LABEL, STATUS_COLUMN_LABEL, TOTAL_COLUMN_LABEL, FAILED_COLUMN_LABEL, INFLIGHT_COLUMN_LABEL, UPTIME_COLUMN_LABEL));
+            out.println(String.format(headerFormat, "-------", "------", "-------", "--------", "----------", "------"));
+            for (Map<String, String> row : camelContexts) {
+                out.println(String.format(rowFormat, row.get("name"), row.get("state"), row.get("exchangesTotal"),
+                        row.get("exchangesFailed"), row.get("exchangesInflight"), row.get("uptime")));
+            }
+        }
+
+        return null;
+    }
+
+    private static Map<String, Integer> computeColumnWidths(final Iterable<Map<String, String>> camelContexts) throws Exception {
+        if (camelContexts == null) {
+            throw new IllegalArgumentException("Unable to determine column widths from null Iterable<CamelContext>");
+        } else {
+            int maxNameLen = 0;
+            int maxStatusLen = 0;
+            int maxTotalLen = 0;
+            int maxFailedLen = 0;
+            int maxInflightLen = 0;
+            int maxUptimeLen = 0;
+
+            for (Map<String, String> row : camelContexts) {
+                final String name = row.get("name");
+                maxNameLen = java.lang.Math.max(maxNameLen, name == null ? 0 : name.length());
+
+                final String status = row.get("state");
+                maxStatusLen = java.lang.Math.max(maxStatusLen, status == null ? 0 : status.length());
+
+                final String total = row.get("exchangesTotal");
+                maxTotalLen = java.lang.Math.max(maxTotalLen, total == null ? 0 : total.length());
+
+                final String failed = row.get("exchangesFailed");
+                maxFailedLen = java.lang.Math.max(maxFailedLen, failed == null ? 0 : failed.length());
+
+                final String inflight = row.get("exchangesInflight");
+                maxInflightLen = java.lang.Math.max(maxInflightLen, inflight == null ? 0 : inflight.length());
+
+                final String uptime = row.get("uptime");
+                maxUptimeLen = java.lang.Math.max(maxUptimeLen, uptime == null ? 0 : uptime.length());
+            }
+
+            final Map<String, Integer> retval = new Hashtable<>();
+            retval.put(CONTEXT_COLUMN_LABEL, maxNameLen);
+            retval.put(STATUS_COLUMN_LABEL, maxStatusLen);
+            retval.put(TOTAL_COLUMN_LABEL, maxTotalLen);
+            retval.put(FAILED_COLUMN_LABEL, maxFailedLen);
+            retval.put(INFLIGHT_COLUMN_LABEL, maxInflightLen);
+            retval.put(UPTIME_COLUMN_LABEL, maxUptimeLen);
+
+            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 contextLen = java.lang.Math.min(columnWidths.get(CONTEXT_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int statusLen = java.lang.Math.min(columnWidths.get(STATUS_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int totalLen = java.lang.Math.min(columnWidths.get(TOTAL_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int failedlLen = java.lang.Math.min(columnWidths.get(FAILED_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int inflightLen = java.lang.Math.min(columnWidths.get(INFLIGHT_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int uptimeLen = java.lang.Math.min(columnWidths.get(UPTIME_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        contextLen = Math.max(MIN_COLUMN_WIDTH, contextLen);
+        statusLen = Math.max(MIN_COLUMN_WIDTH, statusLen);
+        totalLen = Math.max(MIN_COLUMN_WIDTH, totalLen);
+        failedlLen = Math.max(MIN_COLUMN_WIDTH, failedlLen);
+        inflightLen = Math.max(MIN_COLUMN_WIDTH, inflightLen);
+        uptimeLen = Math.max(MIN_COLUMN_WIDTH, uptimeLen);
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(contextLen).append('.').append(contextLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(statusLen).append('.').append(statusLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(totalLen).append('.').append(totalLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(failedlLen).append('.').append(failedlLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(inflightLen).append('.').append(inflightLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(uptimeLen).append('.').append(uptimeLen).append('s').append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextResumeCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextResumeCommand.java
new file mode 100644
index 0000000..e949ad6
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextResumeCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Command to resume a Camel context.
+ */
+public class ContextResumeCommand extends AbstractContextCommand {
+
+    public ContextResumeCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        camelController.resumeContext(contextName);
+        return null;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextStartCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextStartCommand.java
new file mode 100644
index 0000000..63a20da
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextStartCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Command to start a Camel context.
+ */
+public class ContextStartCommand extends AbstractContextCommand {
+
+    public ContextStartCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        camelController.startContext(contextName);
+        return null;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextStopCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextStopCommand.java
new file mode 100644
index 0000000..db33f71
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextStopCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Command to stop a Camel context.
+ */
+public class ContextStopCommand extends AbstractContextCommand {
+
+    public ContextStopCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        camelController.stopContext(contextName);
+        return null;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextSuspendCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextSuspendCommand.java
new file mode 100644
index 0000000..19dcaa4
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextSuspendCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Command to suspend a Camel context.
+ */
+public class ContextSuspendCommand extends AbstractContextCommand {
+
+    public ContextSuspendCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        camelController.suspendContext(contextName);
+        return null;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointListCommand.java
new file mode 100644
index 0000000..18ac8dd
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointListCommand.java
@@ -0,0 +1,169 @@
+/*
+ * 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.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.util.URISupport;
+
+/**
+ * List the Camel endpoints available in the JVM.
+ */
+public class EndpointListCommand extends AbstractCamelCommand {
+
+    private static final String URI_COLUMN_LABEL = "Uri";
+    private static final String STATUS_COLUMN_LABEL = "Status";
+    private static final String CONTEXT_NAME_COLUMN_LABEL = "Context";
+
+    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;
+    // endpoint uris can be very long so clip by default after 120 chars
+    private static final int MAX_COLUMN_WIDTH = 120;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    boolean decode = true;
+    boolean verbose;
+    private final String context;
+
+    public EndpointListCommand(String context, boolean decode, boolean verbose) {
+        this.decode = decode;
+        this.verbose = verbose;
+        this.context = context;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        final List<Map<String, String>> camelContextInfos = camelController.getCamelContexts(this.context);
+        final Map<String, List<Map<String, String>>> contextsToEndpoints = new HashMap<>();
+        
+        for (Map<String, String> camelContextInfo : camelContextInfos) {
+            String camelContextName = camelContextInfo.get("name");
+            final List<Map<String, String>> endpoints = camelController.getEndpoints(camelContextName);
+            if (endpoints.isEmpty()) {
+                continue;
+            }
+            contextsToEndpoints.put(camelContextName, endpoints);
+        }
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(contextsToEndpoints);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToEndpoints.entrySet()) {
+            final String camelContextName = stringListEntry.getKey();
+            final List<Map<String, String>> endpoints = stringListEntry.getValue();
+
+            out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, URI_COLUMN_LABEL, STATUS_COLUMN_LABEL));
+            out.println(String.format(headerFormat, "-------", "---", "------"));
+            for (Map<String, String> row : endpoints) {
+                String uri = row.get("uri");
+                if (decode) {
+                    // decode uri so its more human readable
+                    uri = URLDecoder.decode(uri, "UTF-8");
+                }
+                // sanitize and mask uri so we dont see passwords
+                uri = URISupport.sanitizeUri(uri);
+                String state = row.get("state");
+                out.println(String.format(rowFormat, camelContextName, uri, state));
+            }
+        }
+        return null;
+    }
+
+    private Map<String, Integer> computeColumnWidths(final Map<String, List<Map<String, String>>> contextsToEndpoints) throws Exception {
+        int maxUriLen = 0;
+        int maxStatusLen = 0;
+        int maxCamelContextLen = 0;
+
+        for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToEndpoints.entrySet()) {
+            final String camelContextName = stringListEntry.getKey();
+
+            maxCamelContextLen = java.lang.Math.max(maxCamelContextLen, camelContextName.length());
+            
+            final List<Map<String, String>> endpoints = stringListEntry.getValue();
+
+
+            for (Map<String, String> row : endpoints) {
+                String uri = row.get("uri");
+                if (decode) {
+                    // decode uri so its more human readable
+                    uri = URLDecoder.decode(uri, "UTF-8");
+                }
+                // sanitize and mask uri so we dont see passwords
+                uri = URISupport.sanitizeUri(uri);
+
+                maxUriLen = java.lang.Math.max(maxUriLen, uri == null ? 0 : uri.length());
+
+                final String status = row.get("state");
+                maxStatusLen = java.lang.Math.max(maxStatusLen, status == null ? 0 : status.length());
+            }
+        }
+    
+        final Map<String, Integer> retval = new Hashtable<>();
+        retval.put(URI_COLUMN_LABEL, maxUriLen);
+        retval.put(STATUS_COLUMN_LABEL, maxStatusLen);
+        retval.put(CONTEXT_NAME_COLUMN_LABEL, maxCamelContextLen);
+
+        return retval;
+    }
+
+    private 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 uriLen = java.lang.Math.min(columnWidths.get(URI_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        uriLen = Math.max(MIN_COLUMN_WIDTH, uriLen);
+
+        int ctxLen = java.lang.Math.min(columnWidths.get(CONTEXT_NAME_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        ctxLen = Math.max(MIN_COLUMN_WIDTH, ctxLen);
+        // last row does not have min width
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(ctxLen).append('.').append(ctxLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(uriLen).append('.').append(uriLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%s").append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+    private int getMaxColumnWidth() {
+        if (verbose) {
+            return Integer.MAX_VALUE;
+        } else {
+            return MAX_COLUMN_WIDTH;
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java
new file mode 100644
index 0000000..ab02639
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java
@@ -0,0 +1,249 @@
+/*
+ * 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.net.URLDecoder;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.util.URISupport;
+
+/**
+ * Display endpoint runtime statistics for a CamelContext
+ */
+public class EndpointStatisticCommand extends AbstractCamelCommand {
+
+    private static final String CONTEXT_COLUMN_LABEL = "Context";
+    private static final String URI_COLUMN_LABEL = "Uri";
+    private static final String ROUTE_COLUMN_LABEL = "Route Id";
+    private static final String DIRECTION_COLUMN_LABEL = "Direction";
+    private static final String STATIC_COLUMN_LABEL = "Static";
+    private static final String DYNAMIC_COLUMN_LABEL = "Dynamic";
+    private static final String HITS_COLUMN_LABEL = "Total #";
+
+    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;
+    // endpoint uris can be very long so clip by default after 120 chars
+    private static final int MAX_COLUMN_WIDTH = 120;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    String context;
+    boolean decode = true;
+    private String[] filter;
+
+    public EndpointStatisticCommand(String context, boolean decode, String[] filter) {
+        this.context = context;
+        this.decode = decode;
+        this.filter = filter;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        List<Map<String, String>> contexts = camelController.getCamelContexts(context);
+
+        boolean header = false;
+
+        Map<String, List<Map<String, String>>> allEndpoints = new LinkedHashMap<>();
+
+        for (Map<String, String> context : contexts) {
+            String contextName = context.get("name");
+            List<Map<String, String>> endpoints = camelController.getEndpointRuntimeStatistics(contextName);
+            allEndpoints.put(contextName, endpoints);
+        }
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(allEndpoints);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        for (Map.Entry<String, List<Map<String, String>>> entry : allEndpoints.entrySet()) {
+            String contextName = entry.getKey();
+            for (Map<String, String> row : entry.getValue()) {
+
+                if (!header) {
+                    out.println(String.format(headerFormat, CONTEXT_COLUMN_LABEL, URI_COLUMN_LABEL,
+                                              ROUTE_COLUMN_LABEL, DIRECTION_COLUMN_LABEL, STATIC_COLUMN_LABEL,
+                                              DYNAMIC_COLUMN_LABEL, HITS_COLUMN_LABEL));
+                    out.println(String.format(headerFormat, "-------", "---", "--------",
+                                              "---------", "------", "-------", "-------"));
+                    header = true;
+                }
+
+                String uri = row.get("uri");
+                if (decode) {
+                    // decode uri so its more human readable
+                    uri = URLDecoder.decode(uri, "UTF-8");
+                }
+                // sanitize and mask uri so we dont see passwords
+                uri = URISupport.sanitizeUri(uri);
+                String routeId = row.get("routeId");
+                String direction = row.get("direction");
+                String isStatic = row.get("static");
+                String isDynamic = row.get("dynamic");
+                String hits = row.get("hits");
+
+                // should we filter
+                if (isValidRow(direction, isStatic, isDynamic)) {
+                    out.println(String.format(rowFormat, contextName, uri, routeId, direction, isStatic, isDynamic, hits));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    protected boolean isValidRow(String direction, String isStatic, String isDynamic) {
+        if (filter == null || filter.length == 0) {
+            return true;
+        }
+
+        boolean answer = false;
+        for (String f : filter) {
+            if ("in".equals(f)) {
+                answer = "in".equals(direction);
+            } else if ("out".equals(f)) {
+                answer = "out".equals(direction);
+            } else if ("static".equals(f)) {
+                answer = "true".equals(isStatic);
+            } else if ("dynamic".equals(f)) {
+                answer = "true".equals(isDynamic);
+            }
+            // all filters must apply to accept when multi valued
+            if (!answer) {
+                return false;
+            }
+        }
+        return answer;
+    }
+
+    private Map<String, Integer> computeColumnWidths(Map<String, List<Map<String, String>>> allEndpoints) throws Exception {
+        if (allEndpoints == null) {
+            throw new IllegalArgumentException("Unable to determine column widths from null Iterable<Endpoint>");
+        } else {
+            int maxContextLen = 0;
+            int maxUriLen = 0;
+            int maxRouteIdLen = 0;
+            int maxDirectionLen = 0;
+            int maxStaticLen = 0;
+            int maxDynamicLen = 0;
+            int maxHitsLen = 0;
+
+            for (Map.Entry<String, List<Map<String, String>>> entry : allEndpoints.entrySet()) {
+                String contextName = entry.getKey();
+                for (Map<String, String> row : entry.getValue()) {
+                    maxContextLen = Math.max(maxContextLen, contextName == null ? 0 : contextName.length());
+
+                    String uri = row.get("uri");
+                    if (decode) {
+                        // decode uri so its more human readable
+                        uri = URLDecoder.decode(uri, "UTF-8");
+                    }
+                    // sanitize and mask uri so we dont see passwords
+                    uri = URISupport.sanitizeUri(uri);
+
+                    maxUriLen = Math.max(maxUriLen, uri == null ? 0 : uri.length());
+
+                    final String routeId = row.get("routeId");
+                    maxRouteIdLen = Math.max(maxRouteIdLen, routeId == null ? 0 : routeId.length());
+
+                    final String direction = row.get("direction");
+                    maxDirectionLen = Math.max(maxDirectionLen, direction == null ? 0 : direction.length());
+
+                    final String isStatic = row.get("static");
+                    maxStaticLen = Math.max(maxStaticLen, isStatic == null ? 0 : isStatic.length());
+
+                    final String isDynamic = row.get("dynamic");
+                    maxDynamicLen = Math.max(maxDynamicLen, isDynamic == null ? 0 : isDynamic.length());
+
+                    final String hits = row.get("hits");
+                    maxHitsLen = Math.max(maxHitsLen, hits == null ? 0 : hits.length());
+                }
+            }
+
+            final Map<String, Integer> retval = new Hashtable<>();
+            retval.put(CONTEXT_COLUMN_LABEL, maxContextLen);
+            retval.put(URI_COLUMN_LABEL, maxUriLen);
+            retval.put(ROUTE_COLUMN_LABEL, maxRouteIdLen);
+            retval.put(DIRECTION_COLUMN_LABEL, maxDirectionLen);
+            retval.put(STATIC_COLUMN_LABEL, maxStaticLen);
+            retval.put(DYNAMIC_COLUMN_LABEL, maxDynamicLen);
+            retval.put(HITS_COLUMN_LABEL, maxHitsLen);
+
+            return retval;
+        }
+    }
+
+    private 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 contextLen = Math.min(columnWidths.get(CONTEXT_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        contextLen = Math.max(MIN_COLUMN_WIDTH, contextLen);
+
+        int uriLen = Math.min(columnWidths.get(URI_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        uriLen = Math.max(MIN_COLUMN_WIDTH, uriLen);
+
+        int routeIdLen = Math.min(columnWidths.get(ROUTE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        routeIdLen = Math.max(MIN_COLUMN_WIDTH, routeIdLen);
+
+        int directionLen = Math.min(columnWidths.get(DIRECTION_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        directionLen = Math.max(MIN_COLUMN_WIDTH, directionLen);
+
+        int staticLen = Math.min(columnWidths.get(STATIC_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        staticLen = Math.max(MIN_COLUMN_WIDTH, staticLen);
+
+        int dynamicLen = Math.min(columnWidths.get(DYNAMIC_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        dynamicLen = Math.max(MIN_COLUMN_WIDTH, dynamicLen);
+
+        int totalLen = Math.min(columnWidths.get(HITS_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        totalLen = Math.max(MIN_COLUMN_WIDTH, totalLen);
+
+        // last row does not have min width
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(contextLen).append('.').append(contextLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(uriLen).append('.').append(uriLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(routeIdLen).append('.').append(routeIdLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(directionLen).append('.').append(directionLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(staticLen).append('.').append(staticLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(dynamicLen).append('.').append(dynamicLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(totalLen).append('.').append(totalLen).append('s').append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+    private int getMaxColumnWidth() {
+        return MAX_COLUMN_WIDTH;
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/LocalCamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/LocalCamelController.java
new file mode 100644
index 0000000..6c3cf1f
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/LocalCamelController.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.List;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * A {@link org.apache.camel.commands.CamelController} that runs locally, eg within the same JVM as the {@link CamelContext}s
+ * it manages.
+ *
+ * For example the Apache Camel Karaf commands does this.
+ */
+public interface LocalCamelController extends CamelController {
+
+    /**
+     * Get the list of Camel context.
+     *
+     * @return the list of Camel contexts.
+     * @throws Exception can be thrown
+     */
+    List<CamelContext> getLocalCamelContexts() throws Exception;
+
+    /**
+     * Get a Camel context identified by the given name.
+     *
+     * @param name the Camel context name.
+     * @return the Camel context or null if not found.
+     * @throws Exception can be thrown
+     */
+    CamelContext getLocalCamelContext(String name) throws Exception;
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestApiDocCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestApiDocCommand.java
new file mode 100644
index 0000000..8db7bc4
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestApiDocCommand.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * List the Camel REST services API documentation (requires camel-swagger-java on classpath)
+ */
+public class RestApiDocCommand extends AbstractContextCommand {
+
+    public RestApiDocCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        String json = camelController.getRestApiDocAsJson(contextName);
+        if (json != null) {
+            out.print(json);
+        } else {
+            out.print("There are no REST services");
+        }
+
+        return null;
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestRegistryListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestRegistryListCommand.java
new file mode 100644
index 0000000..e2327d5
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestRegistryListCommand.java
@@ -0,0 +1,198 @@
+/*
+ * 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.net.URLDecoder;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.util.URISupport;
+
+/**
+ * List the Camel REST services from the Rest registry available in the JVM.
+ */
+public class RestRegistryListCommand extends AbstractContextCommand {
+
+    private static final String URL_COLUMN_NAME = "Url";
+    private static final String BASE_PATH_LABEL = "Base Path";
+    private static final String URI_TEMPLATE_LABEL = "Uri Template";
+    private static final String METHOD_COLUMN_LABEL = "Method";
+    private static final String STATE_COLUMN_LABEL = "State";
+    private static final String ROUTE_COLUMN_LABEL = "Route Id";
+
+    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;
+    // endpoint uris can be very long so clip by default after 120 chars
+    private static final int MAX_COLUMN_WIDTH = 120;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    boolean decode = true;
+    boolean verbose;
+
+    public RestRegistryListCommand(String context, boolean decode, boolean verbose) {
+        super(context);
+        this.decode = decode;
+        this.verbose = verbose;
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        List<Map<String, String>> services = camelController.getRestServices(contextName);
+        if (services.isEmpty()) {
+            out.print("There are no REST services");
+            return null;
+        }
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(services);
+        final String headerFormat = buildFormatString(columnWidths, true, verbose);
+        final String rowFormat = buildFormatString(columnWidths, false, verbose);
+
+        if (services.size() > 0) {
+            if (verbose) {
+                out.println(String.format(headerFormat, URL_COLUMN_NAME, BASE_PATH_LABEL, URI_TEMPLATE_LABEL, METHOD_COLUMN_LABEL, STATE_COLUMN_LABEL, ROUTE_COLUMN_LABEL));
+                out.println(String.format(headerFormat, "---", "---------", "------------", "------", "-----", "--------"));
+            } else {
+                out.println(String.format(headerFormat, BASE_PATH_LABEL, URI_TEMPLATE_LABEL, METHOD_COLUMN_LABEL, STATE_COLUMN_LABEL, ROUTE_COLUMN_LABEL));
+                out.println(String.format(headerFormat, "---------", "------------", "------", "-----", "--------"));
+            }
+            for (Map<String, String> row : services) {
+                String uri = null;
+                if (verbose) {
+                    uri = row.get("url");
+                    if (decode) {
+                        // decode uri so its more human readable
+                        uri = URLDecoder.decode(uri, "UTF-8");
+                    }
+                    // sanitize and mask uri so we dont see passwords
+                    uri = URISupport.sanitizeUri(uri);
+                }
+                String basePath = row.get("basePath");
+                String uriTemplate = row.get("uriTemplate") != null ? row.get("uriTemplate") : "";
+                String method = row.get("method");
+                String state = row.get("state");
+                String route = row.get("routeId");
+                if (verbose) {
+                    out.println(String.format(rowFormat, uri, basePath, uriTemplate, method, state, route));
+                } else {
+                    out.println(String.format(rowFormat, basePath, uriTemplate, method, state, route));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Map<String, Integer> computeColumnWidths(List<Map<String, String>> services) throws Exception {
+        int maxUriLen = 0;
+        int maxBasePathLen = 0;
+        int maxUriTemplateLen = 0;
+        int maxMethodLen = 0;
+        int maxStatusLen = 0;
+        int maxRouteLen = 0;
+
+        for (Map<String, String> row : services) {
+            String uri = row.get("url");
+            if (decode) {
+                // decode uri so its more human readable
+                uri = URLDecoder.decode(uri, "UTF-8");
+            }
+            // sanitize and mask uri so we dont see passwords
+            uri = URISupport.sanitizeUri(uri);
+            maxUriLen = Math.max(maxUriLen, uri == null ? 0 : uri.length());
+
+            String basePath = row.get("basePath");
+            maxBasePathLen = Math.max(maxBasePathLen, basePath == null ? 0 : basePath.length());
+
+            String uriTemplate = row.get("uriTemplate");
+            maxUriTemplateLen = Math.max(maxUriTemplateLen, uriTemplate == null ? 0 : uriTemplate.length());
+
+            String method = row.get("method");
+            maxMethodLen = Math.max(maxMethodLen, method == null ? 0 : method.length());
+
+            String status = row.get("state");
+            maxStatusLen = Math.max(maxStatusLen, status == null ? 0 : status.length());
+
+            String routeId = row.get("routeId");
+            maxRouteLen = Math.max(maxRouteLen, routeId == null ? 0 : routeId.length());
+        }
+
+        final Map<String, Integer> retval = new Hashtable<>();
+        retval.put(URL_COLUMN_NAME, maxUriLen);
+        retval.put(BASE_PATH_LABEL, maxBasePathLen);
+        retval.put(URI_TEMPLATE_LABEL, maxUriTemplateLen);
+        retval.put(METHOD_COLUMN_LABEL, maxMethodLen);
+        retval.put(STATE_COLUMN_LABEL, maxStatusLen);
+        retval.put(ROUTE_COLUMN_LABEL, maxRouteLen);
+
+        return retval;
+    }
+
+    private String buildFormatString(final Map<String, Integer> columnWidths, final boolean isHeader, final boolean isVerbose) {
+        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 uriLen = Math.min(columnWidths.get(URL_COLUMN_NAME) + columnWidthIncrement, getMaxColumnWidth());
+        int basePathLen = Math.min(columnWidths.get(BASE_PATH_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        int uriTemplateLen = Math.min(columnWidths.get(URI_TEMPLATE_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        int methodLen = Math.min(columnWidths.get(METHOD_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        int statusLen = Math.min(columnWidths.get(STATE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        int routeLen = Math.min(columnWidths.get(ROUTE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        uriLen = Math.max(MIN_COLUMN_WIDTH, uriLen);
+        basePathLen = Math.max(MIN_COLUMN_WIDTH, basePathLen);
+        uriTemplateLen = Math.max(MIN_COLUMN_WIDTH, uriTemplateLen);
+        methodLen = Math.max(MIN_COLUMN_WIDTH, methodLen);
+        statusLen = Math.max(MIN_COLUMN_WIDTH, statusLen);
+
+        // last row does not have min width
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        if (isVerbose) {
+            retval.append(fieldPreamble).append("%-").append(uriLen).append('.').append(uriLen).append('s').append(fieldPostamble).append(' ');
+        }
+        retval.append(fieldPreamble).append("%-").append(basePathLen).append('.').append(basePathLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(uriTemplateLen).append('.').append(uriTemplateLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(methodLen).append('.').append(methodLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(statusLen).append('.').append(statusLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(routeLen).append('.').append(routeLen).append('s').append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+    private int getMaxColumnWidth() {
+        if (verbose) {
+            return Integer.MAX_VALUE;
+        } else {
+            return MAX_COLUMN_WIDTH;
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestShowCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestShowCommand.java
new file mode 100644
index 0000000..fe027f9
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RestShowCommand.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Command to show the REST marshaled in XML.
+ */
+public class RestShowCommand extends AbstractContextCommand {
+
+    public RestShowCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        String xml = camelController.getRestModelAsXml(contextName);
+        if (xml == null) {
+            out.println("There are no REST services in CamelContext with name: " + contextName);
+        } else {
+            out.println(xml);
+        }
+        return null;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteInfoCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteInfoCommand.java
new file mode 100644
index 0000000..fe83b02
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteInfoCommand.java
@@ -0,0 +1,120 @@
+/*
+ * 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.io.StringReader;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.camel.support.dump.RouteStatDump;
+
+import static org.apache.camel.util.ObjectHelper.isEmpty;
+
+/**
+ * Command to display detailed information about a Camel route.
+ */
+public class RouteInfoCommand extends AbstractRouteCommand {
+
+    public static final String XML_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+    public static final String OUTPUT_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    private StringEscape stringEscape;
+
+    public RouteInfoCommand(String route, String context) {
+        super(route, context);
+    }
+
+    /**
+     * Sets the {@link org.apache.camel.commands.StringEscape} to use.
+     */
+    public void setStringEscape(StringEscape stringEscape) {
+        this.stringEscape = stringEscape;
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        out.println(stringEscape.unescapeJava("\u001B[1mCamel Route " + routeId + "\u001B[0m"));
+        out.println(stringEscape.unescapeJava("\tCamel Context: " + contextName));
+
+        String xml = camelController.getRouteStatsAsXml(routeId, contextName, true, false);
+        if (xml != null) {
+            JAXBContext context = JAXBContext.newInstance(RouteStatDump.class);
+            Unmarshaller unmarshaller = context.createUnmarshaller();
+
+            RouteStatDump route = (RouteStatDump) unmarshaller.unmarshal(new StringReader(xml));
+            out.println(stringEscape.unescapeJava("\tState: " + route.getState()));
+            out.println(stringEscape.unescapeJava("\tState: " + route.getState()));
+
+            out.println("");
+            out.println("");
+            out.println(stringEscape.unescapeJava("\u001B[1mStatistics\u001B[0m"));
+            long total = route.getExchangesCompleted() + route.getExchangesFailed();
+            out.println(stringEscape.unescapeJava("\tExchanges Total: " + total));
+            out.println(stringEscape.unescapeJava("\tExchanges Completed: " + route.getExchangesCompleted()));
+            out.println(stringEscape.unescapeJava("\tExchanges Failed: " + route.getExchangesFailed()));
+            out.println(stringEscape.unescapeJava("\tExchanges Inflight: " + route.getExchangesInflight()));
+            out.println(stringEscape.unescapeJava("\tMin Processing Time: " + route.getMinProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tMax Processing Time: " + route.getMaxProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tMean Processing Time: " + route.getMeanProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tTotal Processing Time: " + route.getTotalProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tLast Processing Time: " + route.getLastProcessingTime() + " ms"));
+            out.println(stringEscape.unescapeJava("\tDelta Processing Time: " + route.getDeltaProcessingTime() + " ms"));
+
+            if (isEmpty(route.getStartTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tStart Statistics Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(route.getStartTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tStart Statistics Date: " + text));
+            }
+
+            // Test for null to see if a any exchanges have been processed first to avoid NPE
+            if (isEmpty(route.getResetTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tReset Statistics Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(route.getResetTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tReset Statistics Date: " + text));
+            }
+
+            // Test for null to see if a any exchanges have been processed first to avoid NPE
+            if (isEmpty(route.getFirstExchangeCompletedTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tFirst Exchange Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(route.getFirstExchangeCompletedTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tFirst Exchange Date: " + text));
+            }
+
+            // Test for null to see if a any exchanges have been processed first to avoid NPE
+            if (isEmpty(route.getLastExchangeCompletedTimestamp())) {
+                // Print an empty value for scripting
+                out.println(stringEscape.unescapeJava("\tLast Exchange Date:"));
+            } else {
+                Date date = new SimpleDateFormat(XML_TIMESTAMP_FORMAT).parse(route.getLastExchangeCompletedTimestamp());
+                String text = new SimpleDateFormat(OUTPUT_TIMESTAMP_FORMAT).format(date);
+                out.println(stringEscape.unescapeJava("\tLast Exchange Date: " + text));
+            }
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteListCommand.java
new file mode 100644
index 0000000..9a03694
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteListCommand.java
@@ -0,0 +1,167 @@
+/*
+ * 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 list all Camel routes.
+ */
+public class RouteListCommand extends AbstractCamelCommand {
+
+    private static final String CONTEXT_COLUMN_LABEL = "Context";
+    private static final String ROUTE_COLUMN_LABEL = "Route";
+    private static final String STATUS_COLUMN_LABEL = "Status";
+    private static final String TOTAL_COLUMN_LABEL = "Total #";
+    private static final String FAILED_COLUMN_LABEL = "Failed #";
+    private static final String INFLIGHT_COLUMN_LABEL = "Inflight #";
+    private static final String UPTIME_COLUMN_LABEL = "Uptime";
+
+    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;
+
+    String name;
+
+    public RouteListCommand(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        List<Map<String, String>> routes = camelController.getRoutes(name);
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(routes);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        if (routes.size() > 0) {
+            out.println(String.format(headerFormat, CONTEXT_COLUMN_LABEL, ROUTE_COLUMN_LABEL, STATUS_COLUMN_LABEL, TOTAL_COLUMN_LABEL, FAILED_COLUMN_LABEL, INFLIGHT_COLUMN_LABEL,
+                    UPTIME_COLUMN_LABEL));
+            out.println(String.format(headerFormat, "-------", "-----", "------", "-------", "--------", "----------", "------"));
+            for (Map<String, String> row : routes) {
+                String contextId = row.get("camelContextName");
+                String routeId = row.get("routeId");
+                String state = row.get("state");
+                String total = row.get("exchangesTotal");
+                String failed = row.get("exchangesFailed");
+                String inflight = row.get("exchangesInflight");
+                String uptime = row.get("uptime");
+                out.println(String.format(rowFormat, contextId, routeId, state, total, failed, inflight, uptime));
+            }
+        }
+
+        return null;
+    }
+
+    private static Map<String, Integer> computeColumnWidths(final Iterable<Map<String, String>> routes) throws Exception {
+        if (routes == null) {
+            throw new IllegalArgumentException("Unable to determine column widths from null Iterable<Route>");
+        } else {
+            int maxContextLen = 0;
+            int maxRouteLen = 0;
+            int maxStatusLen = 0;
+            int maxTotalLen = 0;
+            int maxFailedLen = 0;
+            int maxInflightLen = 0;
+            int maxUptimeLen = 0;
+
+            for (Map<String, String> row : routes) {
+                final String contextId = row.get("camelContextName");
+                maxContextLen = java.lang.Math.max(maxContextLen, contextId == null ? 0 : contextId.length());
+
+                final String routeId = row.get("routeId");
+                maxRouteLen = java.lang.Math.max(maxRouteLen, routeId == null ? 0 : routeId.length());
+
+                final String status = row.get("state");
+                maxStatusLen = java.lang.Math.max(maxStatusLen, status == null ? 0 : status.length());
+
+                final String total = row.get("exchangesTotal");
+                maxTotalLen = java.lang.Math.max(maxTotalLen, total == null ? 0 : total.length());
+
+                final String failed = row.get("exchangesFailed");
+                maxFailedLen = java.lang.Math.max(maxFailedLen, failed == null ? 0 : failed.length());
+
+                final String inflight = row.get("exchangesInflight");
+                maxInflightLen = java.lang.Math.max(maxInflightLen, inflight == null ? 0 : inflight.length());
+
+                final String uptime = row.get("uptime");
+                maxUptimeLen = java.lang.Math.max(maxUptimeLen, uptime == null ? 0 : uptime.length());
+            }
+
+            final Map<String, Integer> retval = new Hashtable<>(3);
+            retval.put(CONTEXT_COLUMN_LABEL, maxContextLen);
+            retval.put(ROUTE_COLUMN_LABEL, maxRouteLen);
+            retval.put(STATUS_COLUMN_LABEL, maxStatusLen);
+            retval.put(TOTAL_COLUMN_LABEL, maxTotalLen);
+            retval.put(FAILED_COLUMN_LABEL, maxFailedLen);
+            retval.put(INFLIGHT_COLUMN_LABEL, maxInflightLen);
+            retval.put(UPTIME_COLUMN_LABEL, maxUptimeLen);
+
+            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 contextLen = Math.min(columnWidths.get(CONTEXT_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int routeLen = Math.min(columnWidths.get(ROUTE_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int statusLen = Math.min(columnWidths.get(STATUS_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int totalLen = Math.min(columnWidths.get(TOTAL_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int failedlLen = Math.min(columnWidths.get(FAILED_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int inflightLen = Math.min(columnWidths.get(INFLIGHT_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        int uptimeLen = Math.min(columnWidths.get(UPTIME_COLUMN_LABEL) + columnWidthIncrement, MAX_COLUMN_WIDTH);
+        contextLen = Math.max(MIN_COLUMN_WIDTH, contextLen);
+        routeLen = Math.max(MIN_COLUMN_WIDTH, routeLen);
+        statusLen = Math.max(MIN_COLUMN_WIDTH, statusLen);
+        totalLen = Math.max(MIN_COLUMN_WIDTH, totalLen);
+        failedlLen = Math.max(MIN_COLUMN_WIDTH, failedlLen);
+        inflightLen = Math.max(MIN_COLUMN_WIDTH, inflightLen);
+        uptimeLen = Math.max(MIN_COLUMN_WIDTH, uptimeLen);
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(contextLen).append('.').append(contextLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(routeLen).append('.').append(routeLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(statusLen).append('.').append(statusLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(totalLen).append('.').append(totalLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(failedlLen).append('.').append(failedlLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%").append(inflightLen).append('.').append(inflightLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(uptimeLen).append('.').append(uptimeLen).append('s').append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteProfileCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteProfileCommand.java
new file mode 100644
index 0000000..469a79a
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteProfileCommand.java
@@ -0,0 +1,85 @@
+/*
+ * 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.io.StringReader;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.camel.support.dump.ProcessorStatDump;
+import org.apache.camel.support.dump.RouteStatDump;
+
+/**
+ * Command to display profile information about a Camel route.
+ */
+public class RouteProfileCommand extends AbstractRouteCommand {
+
+    protected static final String HEADER_FORMAT = "%-30s %10s %12s %12s %12s %12s %12s %12s %12s";
+    protected static final String OUTPUT_FORMAT = "%-30s %10d %12d %12d %12d %12d %12d %12d %12d";
+
+    private StringEscape stringEscape;
+    private volatile String previousCamelContextName;
+
+    public RouteProfileCommand(String route, String context) {
+        super(route, context);
+    }
+
+    /**
+     * Sets the {@link org.apache.camel.commands.StringEscape} to use.
+     */
+    public void setStringEscape(StringEscape stringEscape) {
+        this.stringEscape = stringEscape;
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+
+        JAXBContext context = JAXBContext.newInstance(RouteStatDump.class);
+        Unmarshaller unmarshaller = context.createUnmarshaller();
+
+        // write new header for new camel context
+        if (previousCamelContextName == null || !previousCamelContextName.equals(contextName)) {
+            out.println("");
+            out.println(stringEscape.unescapeJava("\u001B[1mProfile\u001B[0m"));
+            out.println(stringEscape.unescapeJava("\tCamel Context: " + contextName));
+            out.println(String.format(HEADER_FORMAT, "Id", "Count", "Last (ms)", "Delta (ms)", "Mean (ms)", "Min (ms)", "Max (ms)", "Total (ms)", "Self (ms)"));
+        }
+
+        String xml = camelController.getRouteStatsAsXml(routeId, contextName, true, true);
+        if (xml != null) {
+            RouteStatDump route = (RouteStatDump) unmarshaller.unmarshal(new StringReader(xml));
+
+            long count = route.getExchangesCompleted() + route.getExchangesFailed();
+            out.println(String.format(OUTPUT_FORMAT, route.getId(), count, route.getLastProcessingTime(), route.getDeltaProcessingTime(),
+                    route.getMeanProcessingTime(), route.getMinProcessingTime(), route.getMaxProcessingTime(), route.getTotalProcessingTime(), route.getSelfProcessingTime()));
+
+            for (ProcessorStatDump ps : route.getProcessorStats()) {
+                // the self time is the total time of the processor itself
+                long selfTime = ps.getTotalProcessingTime();
+                count = ps.getExchangesCompleted() + ps.getExchangesFailed();
+                // indent route id with 2 spaces
+                out.println(String.format(OUTPUT_FORMAT, "  " + ps.getId(), count, ps.getLastProcessingTime(), ps.getDeltaProcessingTime(),
+                        ps.getMeanProcessingTime(), ps.getMinProcessingTime(), ps.getMaxProcessingTime(), ps.getAccumulatedProcessingTime(), selfTime));
+            }
+        }
+
+        // we want to group routes from the same context in the same table
+        previousCamelContextName = contextName;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteResetStatsCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteResetStatsCommand.java
new file mode 100644
index 0000000..c59d8f0
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteResetStatsCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Command to reset route stats.
+ */
+public class RouteResetStatsCommand extends AbstractContextCommand {
+
+    public RouteResetStatsCommand(String context) {
+        super(context);
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        camelController.resetRouteStats(contextName);
+        return null;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteResumeCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteResumeCommand.java
new file mode 100644
index 0000000..fc6a240
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteResumeCommand.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Command to resume a route.
+ */
+public class RouteResumeCommand extends AbstractRouteCommand {
+
+    public RouteResumeCommand(String route, String context) {
+        super(route, context);
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        camelController.resumeRoute(contextName, routeId);
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteShowCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteShowCommand.java
new file mode 100644
index 0000000..a04e5c4
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteShowCommand.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Command to show the route marshaled in XML.
+ */
+public class RouteShowCommand extends AbstractRouteCommand {
+
+    public RouteShowCommand(String route, String context) {
+        super(route, context);
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        String xml = camelController.getRouteModelAsXml(routeId, contextName);
+        if (xml == null) {
+            err.println("Definition of route " + routeId + " not found.");
+        } else {
+            out.println(xml);
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStartCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStartCommand.java
new file mode 100644
index 0000000..bc0bedc
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStartCommand.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Command to start a route.
+ */
+public class RouteStartCommand extends AbstractRouteCommand {
+
+    public RouteStartCommand(String route, String context) {
+        super(route, context);
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        camelController.startRoute(contextName, routeId);
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStepCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStepCommand.java
new file mode 100644
index 0000000..7f6256c
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStepCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.io.StringReader;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.camel.support.dump.RouteStatDump;
+import org.apache.camel.support.dump.StepStatDump;
+
+/**
+ * Command to display step information about a Camel route.
+ */
+public class RouteStepCommand extends AbstractRouteCommand {
+
+    protected static final String HEADER_FORMAT = "%-30s %10s %12s %12s %12s %12s %12s %12s";
+    protected static final String OUTPUT_FORMAT = "%-30s %10d %12d %12d %12d %12d %12d %12d";
+
+    private StringEscape stringEscape;
+    private volatile String previousCamelContextName;
+
+    public RouteStepCommand(String route, String context) {
+        super(route, context);
+    }
+
+    /**
+     * Sets the {@link StringEscape} to use.
+     */
+    public void setStringEscape(StringEscape stringEscape) {
+        this.stringEscape = stringEscape;
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+
+        JAXBContext context = JAXBContext.newInstance(RouteStatDump.class);
+        Unmarshaller unmarshaller = context.createUnmarshaller();
+
+        // write new header for new camel context
+        if (previousCamelContextName == null || !previousCamelContextName.equals(contextName)) {
+            out.println("");
+            out.println(stringEscape.unescapeJava("\u001B[1mStep\u001B[0m"));
+            out.println(stringEscape.unescapeJava("\tCamel Context: " + contextName));
+            out.println(String.format(HEADER_FORMAT, "Id", "Count", "Last (ms)", "Delta (ms)", "Mean (ms)", "Min (ms)", "Max (ms)", "Total (ms)"));
+        }
+
+        String xml = camelController.getStepStatsAsXml(routeId, contextName, true);
+        if (xml != null) {
+            RouteStatDump route = (RouteStatDump) unmarshaller.unmarshal(new StringReader(xml));
+
+            long count = route.getExchangesCompleted() + route.getExchangesFailed();
+            out.println(String.format(OUTPUT_FORMAT, route.getId(), count, route.getLastProcessingTime(), route.getDeltaProcessingTime(),
+                    route.getMeanProcessingTime(), route.getMinProcessingTime(), route.getMaxProcessingTime(), route.getTotalProcessingTime()));
+
+            for (StepStatDump ss : route.getStepStats()) {
+                count = ss.getExchangesCompleted() + ss.getExchangesFailed();
+                // indent step id with 2 spaces
+                out.println(String.format(OUTPUT_FORMAT, "  " + ss.getId(), count, ss.getLastProcessingTime(), ss.getDeltaProcessingTime(),
+                        ss.getMeanProcessingTime(), ss.getMinProcessingTime(), ss.getMaxProcessingTime(), ss.getTotalProcessingTime()));
+            }
+        }
+
+        // we want to group routes from the same context in the same table
+        previousCamelContextName = contextName;
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStopCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStopCommand.java
new file mode 100644
index 0000000..30ac5f7
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteStopCommand.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Command to stop a route.
+ */
+public class RouteStopCommand extends AbstractRouteCommand {
+
+    public RouteStopCommand(String route, String context) {
+        super(route, context);
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        camelController.stopRoute(contextName, routeId);
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteSuspendCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteSuspendCommand.java
new file mode 100644
index 0000000..9b6bfc6
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/RouteSuspendCommand.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Command to suspend a route.
+ */
+public class RouteSuspendCommand extends AbstractRouteCommand {
+
+    public RouteSuspendCommand(String route, String context) {
+        super(route, context);
+    }
+
+    @Override
+    public void executeOnRoute(CamelController camelController, String contextName, String routeId, PrintStream out, PrintStream err) throws Exception {
+        camelController.suspendRoute(contextName, routeId);
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/StringEscape.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/StringEscape.java
new file mode 100644
index 0000000..693364b
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/StringEscape.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * To escape strings.
+ */
+public interface StringEscape {
+
+    String unescapeJava(String str);
+
+    String escapeJava(String str);
+
+    String hex(char ch);
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/TransformerListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/TransformerListCommand.java
new file mode 100644
index 0000000..0326819
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/TransformerListCommand.java
@@ -0,0 +1,198 @@
+/*
+ * 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.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * List the Camel transformers available in the JVM.
+ */
+public class TransformerListCommand extends AbstractCamelCommand {
+
+    private static final String CONTEXT_NAME_COLUMN_LABEL = "Context";
+    private static final String SCHEME_COLUMN_LABEL = "Scheme";
+    private static final String FROM_COLUMN_LABEL = "From";
+    private static final String TO_COLUMN_LABEL = "To";
+    private static final String STATE_COLUMN_LABEL = "State";
+    private static final String DESCRIPTION_COLUMN_LABEL = "Description";
+
+    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;
+    // endpoint uris can be very long so clip by default after 120 chars
+    private static final int MAX_COLUMN_WIDTH = 120;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    boolean decode = true;
+    boolean verbose;
+    boolean explain;
+    private final String context;
+
+    public TransformerListCommand(String context, boolean decode, boolean verbose, boolean explain) {
+        this.decode = decode;
+        this.verbose = verbose;
+        this.explain = explain;
+        this.context = context;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        final List<Map<String, String>> camelContextInfos = camelController.getCamelContexts(this.context);
+        final Map<String, List<Map<String, String>>> contextsToTransformers = new HashMap<>();
+        
+        for (Map<String, String> camelContextInfo : camelContextInfos) {
+            String camelContextName = camelContextInfo.get("name");
+            final List<Map<String, String>> transformers = camelController.getTransformers(camelContextName);
+            if (transformers.isEmpty()) {
+                continue;
+            }
+            contextsToTransformers.put(camelContextName, transformers);
+        }
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(contextsToTransformers);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToTransformers.entrySet()) {
+            final String camelContextName = stringListEntry.getKey();
+            final List<Map<String, String>> transformers = stringListEntry.getValue();
+
+            if (verbose) {
+                out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, SCHEME_COLUMN_LABEL, FROM_COLUMN_LABEL, TO_COLUMN_LABEL, STATE_COLUMN_LABEL, DESCRIPTION_COLUMN_LABEL));
+                out.println(String.format(headerFormat, "-------", "------", "----", "--", "-----", "-----------"));
+            } else {
+                out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, SCHEME_COLUMN_LABEL, FROM_COLUMN_LABEL, TO_COLUMN_LABEL, STATE_COLUMN_LABEL));
+                out.println(String.format(headerFormat, "-------", "------", "----", "--", "-----"));
+            }
+            for (Map<String, String> row : transformers) {
+                String scheme = row.get("scheme");
+                String from = row.get("from");
+                String to = row.get("to");
+                String state = row.get("state");
+                if (verbose) {
+                    String desc = row.get("description");
+                    out.println(String.format(rowFormat, camelContextName, scheme, from, to, state, desc));
+                } else {
+                    out.println(String.format(rowFormat, camelContextName, scheme, from, to, state));
+                }
+            }
+        }
+        return null;
+    }
+
+    private Map<String, Integer> computeColumnWidths(final Map<String, List<Map<String, String>>> contextsToTransformers) throws Exception {
+        int maxCamelContextLen = 0;
+        int maxSchemeLen = 0;
+        int maxFromLen = 0;
+        int maxToLen = 0;
+        int maxStatusLen = 0;
+        int maxDescLen = 0;
+
+        for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToTransformers.entrySet()) {
+            final String camelContextName = stringListEntry.getKey();
+
+            maxCamelContextLen = java.lang.Math.max(maxCamelContextLen, camelContextName.length());
+            
+            final List<Map<String, String>> transformers = stringListEntry.getValue();
+
+
+            for (Map<String, String> row : transformers) {
+                String scheme = row.get("scheme");
+                maxSchemeLen = java.lang.Math.max(maxSchemeLen, scheme == null ? 0 : scheme.length());
+                String from = row.get("from");
+                maxFromLen = java.lang.Math.max(maxFromLen, from == null ? 0 : from.length());
+                String to = row.get("to");
+                maxToLen = java.lang.Math.max(maxToLen, to == null ? 0 : to.length());
+                String status = row.get("state");
+                maxStatusLen = java.lang.Math.max(maxStatusLen, status == null ? 0 : status.length());
+                if (verbose) {
+                    String desc = row.get("description");
+                    maxDescLen = java.lang.Math.max(maxDescLen, desc == null ? 0 : desc.length());
+                }
+            }
+        }
+    
+        final Map<String, Integer> retval = new Hashtable<>();
+        retval.put(CONTEXT_NAME_COLUMN_LABEL, maxCamelContextLen);
+        retval.put(SCHEME_COLUMN_LABEL, maxSchemeLen);
+        retval.put(FROM_COLUMN_LABEL, maxFromLen);
+        retval.put(TO_COLUMN_LABEL, maxToLen);
+        retval.put(STATE_COLUMN_LABEL, maxStatusLen);
+        if (verbose) {
+            retval.put(DESCRIPTION_COLUMN_LABEL, maxDescLen);
+        }
+
+        return retval;
+    }
+
+    private 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 ctxLen = java.lang.Math.min(columnWidths.get(CONTEXT_NAME_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        ctxLen = Math.max(MIN_COLUMN_WIDTH, ctxLen);
+        int schemeLen = java.lang.Math.min(columnWidths.get(SCHEME_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        schemeLen = Math.max(MIN_COLUMN_WIDTH, schemeLen);
+        int fromLen = java.lang.Math.min(columnWidths.get(FROM_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        fromLen = Math.max(MIN_COLUMN_WIDTH, fromLen);
+        int toLen = java.lang.Math.min(columnWidths.get(TO_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        toLen = Math.max(MIN_COLUMN_WIDTH, toLen);
+        int stateLen = -1;
+        if (verbose) {
+            stateLen = java.lang.Math.min(columnWidths.get(STATE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+            stateLen = Math.max(MIN_COLUMN_WIDTH, stateLen);
+        }
+        // last row does not have min width
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(ctxLen).append('.').append(ctxLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(schemeLen).append('.').append(schemeLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(fromLen).append('.').append(fromLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(toLen).append('.').append(toLen).append('s').append(fieldPostamble).append(' ');
+        if (verbose) {
+            retval.append(fieldPreamble).append("%-").append(stateLen).append('.').append(stateLen).append('s').append(fieldPostamble).append(' ');
+        }
+        retval.append(fieldPreamble).append("%s").append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+    private int getMaxColumnWidth() {
+        if (verbose) {
+            return Integer.MAX_VALUE;
+        } else {
+            return MAX_COLUMN_WIDTH;
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java
new file mode 100644
index 0000000..205d00d
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java
@@ -0,0 +1,180 @@
+/*
+ * 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.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * List the Camel validators available in the JVM.
+ */
+public class ValidatorListCommand extends AbstractCamelCommand {
+
+    private static final String CONTEXT_NAME_COLUMN_LABEL = "Context";
+    private static final String TYPE_COLUMN_LABEL = "Type";
+    private static final String STATE_COLUMN_LABEL = "State";
+    private static final String DESCRIPTION_COLUMN_LABEL = "Description";
+
+    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;
+    // endpoint uris can be very long so clip by default after 120 chars
+    private static final int MAX_COLUMN_WIDTH = 120;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    boolean decode = true;
+    boolean verbose;
+    boolean explain;
+    private final String context;
+
+    public ValidatorListCommand(String context, boolean decode, boolean verbose, boolean explain) {
+        this.decode = decode;
+        this.verbose = verbose;
+        this.explain = explain;
+        this.context = context;
+    }
+
+    @Override
+    public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception {
+        final List<Map<String, String>> camelContextInfos = camelController.getCamelContexts(this.context);
+        final Map<String, List<Map<String, String>>> contextsToValidators = new HashMap<>();
+        
+        for (Map<String, String> camelContextInfo : camelContextInfos) {
+            String camelContextName = camelContextInfo.get("name");
+            final List<Map<String, String>> validators = camelController.getValidators(camelContextName);
+            if (validators.isEmpty()) {
+                continue;
+            }
+            contextsToValidators.put(camelContextName, validators);
+        }
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(contextsToValidators);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToValidators.entrySet()) {
+            final String camelContextName = stringListEntry.getKey();
+            final List<Map<String, String>> validators = stringListEntry.getValue();
+
+            if (verbose) {
+                out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, TYPE_COLUMN_LABEL, STATE_COLUMN_LABEL, DESCRIPTION_COLUMN_LABEL));
+                out.println(String.format(headerFormat, "-------", "----", "-----", "-----------"));
+            } else {
+                out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, TYPE_COLUMN_LABEL, STATE_COLUMN_LABEL));
+                out.println(String.format(headerFormat, "-------", "----", "-----"));
+            }
+            for (Map<String, String> row : validators) {
+                String type = row.get("type");
+                String state = row.get("state");
+                if (verbose) {
+                    String desc = row.get("description");
+                    out.println(String.format(rowFormat, camelContextName, type, state, desc));
+                } else {
+                    out.println(String.format(rowFormat, camelContextName, type, state));
+                }
+            }
+        }
+        return null;
+    }
+
+    private Map<String, Integer> computeColumnWidths(final Map<String, List<Map<String, String>>> contextsToValidators) throws Exception {
+        int maxCamelContextLen = 0;
+        int maxTypeLen = 0;
+        int maxStatusLen = 0;
+        int maxDescLen = 0;
+
+        for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToValidators.entrySet()) {
+            final String camelContextName = stringListEntry.getKey();
+
+            maxCamelContextLen = java.lang.Math.max(maxCamelContextLen, camelContextName.length());
+            
+            final List<Map<String, String>> validators = stringListEntry.getValue();
+
+
+            for (Map<String, String> row : validators) {
+                String type = row.get("type");
+                maxTypeLen = java.lang.Math.max(maxTypeLen, type == null ? 0 : type.length());
+                String status = row.get("state");
+                maxStatusLen = java.lang.Math.max(maxStatusLen, status == null ? 0 : status.length());
+                if (verbose) {
+                    String desc = row.get("description");
+                    maxDescLen = java.lang.Math.max(maxDescLen, desc == null ? 0 : desc.length());
+                }
+            }
+        }
+    
+        final Map<String, Integer> retval = new Hashtable<>();
+        retval.put(CONTEXT_NAME_COLUMN_LABEL, maxCamelContextLen);
+        retval.put(TYPE_COLUMN_LABEL, maxTypeLen);
+        retval.put(STATE_COLUMN_LABEL, maxStatusLen);
+        if (verbose) {
+            retval.put(DESCRIPTION_COLUMN_LABEL, maxDescLen);
+        }
+
+        return retval;
+    }
+
+    private 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 ctxLen = java.lang.Math.min(columnWidths.get(CONTEXT_NAME_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        ctxLen = Math.max(MIN_COLUMN_WIDTH, ctxLen);
+        int typeLen = java.lang.Math.min(columnWidths.get(TYPE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        typeLen = Math.max(MIN_COLUMN_WIDTH, typeLen);
+        int stateLen = -1;
+        if (verbose) {
+            stateLen = java.lang.Math.min(columnWidths.get(STATE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+            stateLen = Math.max(MIN_COLUMN_WIDTH, stateLen);
+        }
+        // last row does not have min width
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(ctxLen).append('.').append(ctxLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(typeLen).append('.').append(typeLen).append('s').append(fieldPostamble).append(' ');
+        if (verbose) {
+            retval.append(fieldPreamble).append("%-").append(stateLen).append('.').append(stateLen).append('s').append(fieldPostamble).append(' ');
+        }
+        retval.append(fieldPreamble).append("%s").append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+    private int getMaxColumnWidth() {
+        if (verbose) {
+            return Integer.MAX_VALUE;
+        } else {
+            return MAX_COLUMN_WIDTH;
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/internal/MatchUtil.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/internal/MatchUtil.java
new file mode 100644
index 0000000..aa03d4b
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/internal/MatchUtil.java
@@ -0,0 +1,31 @@
+/*
+ * 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.internal;
+
+/**
+ * Util class.
+ */
+public final class MatchUtil {
+
+    private MatchUtil() {
+    }
+
+    public static boolean matchWildcard(String name, String pattern) {
+        return pattern.endsWith("*") && name.startsWith(pattern.substring(0, pattern.length() - 1));
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/internal/RegexUtil.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/internal/RegexUtil.java
new file mode 100644
index 0000000..9fe5d7a
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/internal/RegexUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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.internal;
+
+/**
+ * Util class.
+ */
+public final class RegexUtil {
+
+    private RegexUtil() {
+    }
+
+    /**
+     * convert a wild card containing * and ? to the equivalent regex
+     *
+     * @param wildcard wildcard string describing a file.
+     * @return regex string that could be fed to Pattern.compile
+     */
+    public static String wildcardAsRegex(String wildcard) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < wildcard.length(); i++) {
+            final char c = wildcard.charAt(i);
+            switch (c) {
+                case '*':
+                    sb.append(".*?");
+                    break;
+                case '?':
+                    sb.append(".");
+                    break;
+                // chars that have magic regex meaning. They need quoting to be taken literally
+                case '$':
+                case '(':
+                case ')':
+                case '+':
+                case '-':
+                case '.':
+                case '[':
+                case '\\':
+                case ']':
+                case '^':
+                case '{':
+                case '|':
+                case '}':
+                    sb.append('\\');
+                    sb.append(c);
+                    break;
+                default:
+                    sb.append(c);
+                    break;
+            }
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/main/resources/META-INF/LICENSE.txt b/platforms/commands/commands-core/src/main/resources/META-INF/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/resources/META-INF/LICENSE.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
+
diff --git a/platforms/commands/commands-core/src/main/resources/META-INF/NOTICE.txt b/platforms/commands/commands-core/src/main/resources/META-INF/NOTICE.txt
new file mode 100644
index 0000000..2e215bf
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/resources/META-INF/NOTICE.txt
@@ -0,0 +1,11 @@
+   =========================================================================
+   ==  NOTICE file corresponding to the section 4 d of                    ==
+   ==  the Apache License, Version 2.0,                                   ==
+   ==  in this case for the Apache Camel distribution.                    ==
+   =========================================================================
+
+   This product includes software developed by
+   The Apache Software Foundation (http://www.apache.org/).
+
+   Please read the different LICENSE files present in the licenses directory of
+   this distribution.
diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java
new file mode 100644
index 0000000..4053c35
--- /dev/null
+++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Message;
+import org.apache.camel.ValidationException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.engine.ExplicitCamelContextNameStrategy;
+import org.apache.camel.spi.DataType;
+import org.apache.camel.spi.Transformer;
+import org.apache.camel.spi.Validator;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class AbstractLocalCamelControllerTest {
+
+    private final DummyCamelController localCamelController;
+
+    private final CamelContext context;
+
+    public AbstractLocalCamelControllerTest() throws Exception {
+        context = new DefaultCamelContext();
+        context.setNameStrategy(new ExplicitCamelContextNameStrategy("context1"));
+        context.getInflightRepository().setInflightBrowseEnabled(true);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                transformer()
+                    .fromType("xml:foo")
+                    .toType("json:bar")
+                    .withJava(DummyTransformer.class);
+                validator()
+                    .type("xml:foo")
+                    .withJava(DummyValidator.class);
+                from("direct:start1").id("route1").delay(100).to("mock:result1");
+                from("direct:start2").id("route2").delay(100).to("mock:result2");
+                from("direct:start3").id("route3").delay(100).to("mock:result3");
+            }
+        });
+        localCamelController = new DummyCamelController(context);
+    }
+
+    @Before
+    public void startContext() throws Exception {
+        context.start();
+    }
+
+    @After
+    public void stopContext() throws Exception {
+        context.stop();
+    }
+
+    @Test
+    public void testBrowseInflightExchangesWithMoreRoutes() throws Exception {
+        context.createProducerTemplate().asyncSendBody("direct:start1", "Start one");
+        context.createProducerTemplate().asyncSendBody("direct:start2", "Start two");
+        context.createProducerTemplate().asyncSendBody("direct:start3", "Start three");
+
+        // let the exchange proceed
+        Thread.sleep(50);
+
+        final List<Map<String, Object>> inflightExchanges = localCamelController.browseInflightExchanges("context1", null, 0, false);
+
+        assertEquals("Context should contain three inflight exchanges", 3, inflightExchanges.size());
+    }
+
+    @Test
+    public void testBrowseInflightExchangesWithNoRoutes() throws Exception {
+        final List<Map<String, Object>> inflightExchanges = localCamelController.browseInflightExchanges("context1", null, 0, false);
+
+        assertTrue("Context without routes should not have any inflight exchanges", inflightExchanges.isEmpty());
+    }
+
+    @Test
+    public void testBrowseInflightExchangesWithOneRoute() throws Exception {
+        context.createProducerTemplate().asyncSendBody("direct:start1", "Start one");
+
+        // let the exchange proceed
+        Thread.sleep(50);
+
+        final List<Map<String, Object>> inflightExchanges = localCamelController.browseInflightExchanges("context1", null, 0, false);
+
+        assertEquals("Context should contain one inflight exchange", 1, inflightExchanges.size());
+    }
+
+    @Test
+    public void testBrowseInflightExchangesWithSpecificRoute() throws Exception {
+        context.createProducerTemplate().asyncSendBody("direct:start1", "Start one");
+        context.createProducerTemplate().asyncSendBody("direct:start2", "Start two");
+        context.createProducerTemplate().asyncSendBody("direct:start3", "Start three");
+
+        // let the exchanges proceed
+        Thread.sleep(50);
+
+        final List<Map<String, Object>> inflightExchanges = localCamelController.browseInflightExchanges("context1", "route2", 0, false);
+
+        assertEquals("Context should contain one inflight exchange for specific route", 1, inflightExchanges.size());
+    }
+
+    @Test
+    public void testTransformer() throws Exception {
+        List<Map<String, String>> transformers = localCamelController.getTransformers("context1");
+        assertEquals(1, transformers.size());
+        Map<String, String> dummyTransformer = transformers.get(0);
+        assertEquals("context1", dummyTransformer.get("camelContextName"));
+        assertEquals("DummyTransformer[scheme='null', from='xml:foo', to='json:bar']", dummyTransformer.get("description"));
+        assertEquals(null, dummyTransformer.get("scheme"));
+        assertEquals("xml:foo", dummyTransformer.get("from"));
+        assertEquals("json:bar", dummyTransformer.get("to"));
+        assertEquals("Started", dummyTransformer.get("state"));
+    }
+
+    @Test
+    public void testValidator() throws Exception {
+        List<Map<String, String>> validators = localCamelController.getValidators("context1");
+        assertEquals(1, validators.size());
+        Map<String, String> dummyValidator = validators.get(0);
+        assertEquals("context1", dummyValidator.get("camelContextName"));
+        assertEquals("DummyValidator[type='xml:foo']", dummyValidator.get("description"));
+        assertEquals("xml:foo", dummyValidator.get("type"));
+        assertEquals("Started", dummyValidator.get("state"));
+    }
+
+    public static class DummyTransformer extends Transformer {
+        @Override
+        public void transform(Message message, DataType from, DataType to) throws Exception {
+        }
+    }
+
+    public static class DummyValidator extends Validator {
+        @Override
+        public void validate(Message message, DataType type) throws ValidationException {
+        }
+    }
+}
diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ContextListCommandTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ContextListCommandTest.java
new file mode 100644
index 0000000..08a175c
--- /dev/null
+++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ContextListCommandTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.engine.DefaultRuntimeEndpointRegistry;
+import org.apache.camel.impl.engine.ExplicitCamelContextNameStrategy;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ContextListCommandTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ContextListCommandTest.class);
+
+    @Test
+    public void testContextList() throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.setNameStrategy(new ExplicitCamelContextNameStrategy("foobar"));
+        context.start();
+
+        CamelController controller = new DummyCamelController(context);
+
+        OutputStream os = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(os);
+
+        ContextListCommand command = new ContextListCommand();
+        command.execute(controller, ps, null);
+
+        String out = os.toString();
+        assertNotNull(out);
+        LOG.info("\n\n{}\n", out);
+
+        // should contain a table with the context
+        assertTrue(out.contains("foobar"));
+        assertTrue(out.contains("Started"));
+
+        context.stop();
+    }
+
+    @Test
+    public void testEndpointStats() throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.setRuntimeEndpointRegistry(new DefaultRuntimeEndpointRegistry());
+        context.setNameStrategy(new ExplicitCamelContextNameStrategy("foobar"));
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start").to("mock:result");
+            }
+        });
+        context.start();
+
+        context.createProducerTemplate().sendBody("direct:start", "Hello World");
+
+        CamelController controller = new DummyCamelController(context);
+
+        OutputStream os = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(os);
+
+        EndpointStatisticCommand command = new EndpointStatisticCommand("foobar", false, null);
+        command.execute(controller, ps, null);
+
+        String out = os.toString();
+        assertNotNull(out);
+        LOG.info("\n\n{}\n", out);
+
+        assertTrue(out.contains("direct://start"));
+        assertTrue(out.contains("mock://result"));
+
+        context.stop();
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/DummyCamelController.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/DummyCamelController.java
new file mode 100644
index 0000000..e53e88c
--- /dev/null
+++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/DummyCamelController.java
@@ -0,0 +1,51 @@
+/*
+ * 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.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+
+public class DummyCamelController extends AbstractLocalCamelController {
+
+    private CamelContext camelContext;
+
+    public DummyCamelController(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    public List<CamelContext> getLocalCamelContexts() {
+        List<CamelContext> answer = new ArrayList<>(1);
+        answer.add(camelContext);
+        return answer;
+    }
+
+    @Override
+    public List<Map<String, String>> getCamelContexts() throws Exception {
+        List<Map<String, String>> answer = new ArrayList<>(1);
+        Map<String, String> row = new LinkedHashMap<>();
+        row.put("name", camelContext.getName());
+        row.put("state", camelContext.getStatus().name());
+        row.put("uptime", camelContext.getUptime());
+        answer.add(row);
+        return answer;
+    }
+}
diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java
new file mode 100644
index 0000000..dd4ceff
--- /dev/null
+++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Message;
+import org.apache.camel.ValidationException;
+import org.apache.camel.builder.ExpressionBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.engine.ExplicitCamelContextNameStrategy;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.language.ExpressionDefinition;
+import org.apache.camel.model.validator.CustomValidatorDefinition;
+import org.apache.camel.model.validator.EndpointValidatorDefinition;
+import org.apache.camel.model.validator.PredicateValidatorDefinition;
+import org.apache.camel.spi.DataType;
+import org.apache.camel.spi.Validator;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ValidatorListCommandTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ValidatorListCommandTest.class);
+
+    @Test
+    public void testValidatorList() throws Exception {
+        String out = doTest(false);
+        assertTrue(out.contains("xml:foo"));
+        assertTrue(out.contains("java:" + this.getClass().getName()));
+        assertTrue(out.contains("custom"));
+        assertTrue(out.contains("Started"));
+        assertFalse(out.contains("ProcessorValidator["));
+        assertFalse(out.contains("processor='validate(body)'"));
+        assertFalse(out.contains("processor='sendTo(direct://validator)'"));
+        assertFalse(out.contains("MyValidator["));
+    }
+    
+    @Test
+    public void testValidatorListVerbose() throws Exception {
+        String out = doTest(true);
+        assertTrue(out.contains("xml:foo"));
+        assertTrue(out.contains("java:" + this.getClass().getName()));
+        assertTrue(out.contains("custom"));
+        assertTrue(out.contains("Started"));
+        assertTrue(out.contains("ProcessorValidator["));
+        assertTrue(out.contains("processor='validate(body)'"));
+        //assertTrue(out.contains("processor='sendTo(direct://validator)'"));
+        assertTrue(out.contains("MyValidator["));
+    }
+    
+    private String doTest(boolean verbose) throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        EndpointValidatorDefinition evd = new EndpointValidatorDefinition();
+        evd.setType("xml:foo");
+        evd.setUri("direct:validator");
+        context.getExtension(Model.class).getValidators().add(evd);
+        PredicateValidatorDefinition pvd = new PredicateValidatorDefinition();
+        pvd.setType(this.getClass());
+        pvd.setExpression(new ExpressionDefinition(ExpressionBuilder.bodyExpression()));
+        context.getExtension(Model.class).getValidators().add(pvd);
+        CustomValidatorDefinition cvd = new CustomValidatorDefinition();
+        cvd.setType("custom");
+        cvd.setClassName(MyValidator.class.getName());
+        context.getExtension(Model.class).getValidators().add(cvd);
+        context.setNameStrategy(new ExplicitCamelContextNameStrategy("foobar"));
+        context.start();
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:foo")
+                        .to("mock:foo");
+            }
+        });
+
+        CamelController controller = new DummyCamelController(context);
+
+        OutputStream os = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(os);
+
+        ValidatorListCommand command = new ValidatorListCommand(null, false, verbose, false);
+        command.execute(controller, ps, null);
+
+        String out = os.toString();
+        assertNotNull(out);
+        LOG.info("\n\n{}\n", out);
+
+        context.stop();
+        return out;
+    }
+
+    public static class MyValidator extends Validator {
+        @Override
+        public void validate(Message message, DataType type) throws ValidationException {
+            return;
+        }
+    }
+}
+
diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/internal/RegexUtilTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/internal/RegexUtilTest.java
new file mode 100644
index 0000000..26916f5
--- /dev/null
+++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/internal/RegexUtilTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal;
+
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class RegexUtilTest {
+
+    @Test
+    public void testWildcardAsRegex() throws Exception {
+        String testRouteId1 = "route.inbound.systema";
+        String testRouteId2 = "route.inbound.systemb";
+        String testRouteId3 = "route.outbound.systema";
+        String testRouteId4 = "route.outbound.systemb";
+        String testRouteId5 = "outbound.systemc";
+
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("route.inbound*"), testRouteId1));
+        assertTrue(!Pattern.matches(RegexUtil.wildcardAsRegex(".inbound*"), testRouteId2));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*.inbound*"), testRouteId2));
+
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*outbound*"), testRouteId3));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*outbound*"), testRouteId4));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*outbound*"), testRouteId5));
+
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*"), testRouteId1));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*"), testRouteId2));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*"), testRouteId3));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*"), testRouteId4));
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("*"), testRouteId5));
+
+        assertTrue(Pattern.matches(RegexUtil.wildcardAsRegex("route.inbound.systema"), testRouteId1));
+    }
+
+}
diff --git a/platforms/commands/commands-core/src/test/resources/log4j2.properties b/platforms/commands/commands-core/src/test/resources/log4j2.properties
new file mode 100644
index 0000000..03a7c00
--- /dev/null
+++ b/platforms/commands/commands-core/src/test/resources/log4j2.properties
@@ -0,0 +1,30 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+appender.file.type = File
+appender.file.name = file
+appender.file.fileName = target/camel-commands-core-test.log
+appender.file.layout.type = PatternLayout
+appender.file.layout.pattern = %d %-5p %c{1} - %m %n
+appender.out.type = Console
+appender.out.name = out
+appender.out.layout.type = PatternLayout
+appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n
+logger.commands.name = org.apache.camel.commands
+logger.commands.level = DEBUG
+rootLogger.level = INFO
+rootLogger.appenderRef.file.ref = file
diff --git a/platforms/commands/pom.xml b/platforms/commands/pom.xml
new file mode 100644
index 0000000..f590784
--- /dev/null
+++ b/platforms/commands/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.camel.karaf</groupId>
+        <artifactId>platforms</artifactId>
+        <version>3.2.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>commands</artifactId>
+    <packaging>pom</packaging>
+    <name>Camel Karaf :: Platforms :: Commands</name>
+
+    <modules>
+        <module>commands-core</module>
+    </modules>
+
+</project>
diff --git a/platforms/pom.xml b/platforms/pom.xml
index 8280cfd..9949b14 100644
--- a/platforms/pom.xml
+++ b/platforms/pom.xml
@@ -31,6 +31,7 @@
     <name>Camel Karaf :: Platforms </name>
 
     <modules>
+        <module>commands</module>
         <module>karaf</module>
     </modules>