You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2017/10/12 18:03:37 UTC

[2/6] camel git commit: Moved over camel route coverage maven plugin to master branch.

Moved over camel route coverage maven plugin to master branch.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/f015f7b0
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/f015f7b0
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/f015f7b0

Branch: refs/heads/master
Commit: f015f7b0d3a33712454637f2d9d8f24a3743adca
Parents: 9ffb254
Author: Claus Ibsen <da...@apache.org>
Authored: Thu Oct 12 19:20:05 2017 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Thu Oct 12 19:21:23 2017 +0200

----------------------------------------------------------------------
 .../mbean/RouteCoverageXmlParser.java           |  17 +-
 .../test/spring/CamelAnnotationsHandler.java    |  25 +
 .../CamelSpringBootExecutionListener.java       |  11 +-
 .../CamelSpringDelegatingTestContextLoader.java |  13 +
 .../spring/CamelSpringTestContextLoader.java    |  45 +-
 ...gTestContextLoaderTestExecutionListener.java |   3 +-
 .../test/spring/CamelSpringTestHelper.java      |  14 +-
 .../camel/test/spring/EnableRouteCoverage.java  |  39 ++
 .../test/spring/RouteCoverageEventNotifier.java |  98 ++++
 ...ringRouteProcessorDumpRouteCoverageTest.java |  55 +++
 .../test/spring/CamelSpringRunnerPlainTest.java |   5 +
 .../camel/test/junit4/CamelTestSupport.java     |   4 +-
 examples/camel-example-spring-boot-xml/pom.xml  | 151 ++++++
 .../camel-example-spring-boot-xml/readme.adoc   |  41 ++
 .../src/main/java/sample/camel/SampleBean.java  |  38 ++
 .../sample/camel/SampleCamelApplication.java    |  40 ++
 .../src/main/resources/application.properties   |  46 ++
 .../src/main/resources/my-camel.xml             |  40 ++
 .../java/sample/camel/FooApplicationTest.java   |  52 ++
 .../camel/SampleCamelApplicationTest.java       |  49 ++
 examples/camel-example-spring-boot/pom.xml      |  12 +
 .../java/sample/camel/SampleCamelRouter.java    |   7 +-
 .../java/sample/camel/FooApplicationTest.java   |  52 ++
 .../camel/SampleCamelApplicationTest.java       |   2 +
 examples/pom.xml                                |   1 +
 tooling/camel-route-parser/pom.xml              |   7 +
 .../apache/camel/parser/RouteBuilderParser.java |  39 ++
 .../org/apache/camel/parser/XmlRouteParser.java |  57 +++
 .../apache/camel/parser/graph/RenderRoute.java  |  70 +++
 .../helper/CamelJavaTreeParserHelper.java       | 478 +++++++++++++++++++
 .../parser/helper/CamelXmlTreeParserHelper.java | 134 ++++++
 .../parser/helper/RouteCoverageHelper.java      | 115 +++++
 .../camel/parser/model/CamelNodeDetails.java    | 165 +++++++
 .../parser/model/CamelNodeDetailsFactory.java   |  37 ++
 .../apache/camel/parser/model/CoverageData.java |  39 ++
 .../parser/java/MyJavaDslRouteBuilder.java      |  39 ++
 .../camel/parser/java/RoasterJavaDslTest.java   |  77 +++
 .../java/RoasterJavaDslTwoRoutesTest.java       |  86 ++++
 .../parser/java/TwoRoutesRouteBuilder.java      |  33 ++
 .../camel/parser/xml/XmlParseTreeTest.java      |  58 +++
 .../org/apache/camel/parser/xml/mycamel.xml     |   2 +-
 .../apache/camel/maven/RouteCoverageMojo.java   | 408 ++++++++++++++++
 .../camel/maven/model/RouteCoverageNode.java    |  77 +++
 43 files changed, 2764 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/camel-core/src/main/java/org/apache/camel/management/mbean/RouteCoverageXmlParser.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/management/mbean/RouteCoverageXmlParser.java b/camel-core/src/main/java/org/apache/camel/management/mbean/RouteCoverageXmlParser.java
index 4633ece..a28ff71 100644
--- a/camel-core/src/main/java/org/apache/camel/management/mbean/RouteCoverageXmlParser.java
+++ b/camel-core/src/main/java/org/apache/camel/management/mbean/RouteCoverageXmlParser.java
@@ -96,7 +96,20 @@ public final class RouteCoverageXmlParser {
                                 el.setAttribute("totalProcessingTime", "" + totalTime);
                             }
                         } else if ("from".equals(qName)) {
-                            // TODO: include the stats from the route mbean as that would be the same
+                            // grab statistics from the parent route as from would be the same
+                            Element parent = elementStack.peek();
+                            if (parent != null) {
+                                String routeId = parent.getAttribute("id");
+                                ManagedRouteMBean route = camelContext.getManagedRoute(routeId, ManagedRouteMBean.class);
+                                if (route != null) {
+                                    long total = route.getExchangesTotal();
+                                    el.setAttribute("exchangesTotal", "" + total);
+                                    long totalTime = route.getTotalProcessingTime();
+                                    el.setAttribute("totalProcessingTime", "" + totalTime);
+                                    // from is index-0
+                                    el.setAttribute("index", "0");
+                                }
+                            }
                         } else {
                             ManagedProcessorMBean processor = camelContext.getManagedProcessor(id, ManagedProcessorMBean.class);
                             if (processor != null) {
@@ -104,6 +117,8 @@ public final class RouteCoverageXmlParser {
                                 el.setAttribute("exchangesTotal", "" + total);
                                 long totalTime = processor.getTotalProcessingTime();
                                 el.setAttribute("totalProcessingTime", "" + totalTime);
+                                int index = processor.getIndex();
+                                el.setAttribute("index", "" + index);
                             }
                         }
                     } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
index 8902f4f..071891f 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
@@ -23,6 +23,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 
 import org.apache.camel.component.properties.PropertiesComponent;
 import org.apache.camel.impl.DefaultDebugger;
@@ -30,6 +31,7 @@ import org.apache.camel.impl.InterceptSendToMockEndpointStrategy;
 import org.apache.camel.management.JmxSystemPropertyKeys;
 import org.apache.camel.spi.Breakpoint;
 import org.apache.camel.spi.Debugger;
+import org.apache.camel.spi.EventNotifier;
 import org.apache.camel.spring.SpringCamelContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -69,6 +71,29 @@ public final class CamelAnnotationsHandler {
         }
     }
 
+    /**
+     * Handles disabling of JMX on Camel contexts based on {@link DisableJmx}.
+     *
+     * @param context the initialized Spring context
+     * @param testClass the test class being executed
+     */
+    public static void handleRouteCoverage(ConfigurableApplicationContext context, Class<?> testClass, Function testMethod) throws Exception {
+        if (testClass.isAnnotationPresent(EnableRouteCoverage.class)) {
+            System.setProperty("CamelTestRouteCoverage", "true");
+
+            CamelSpringTestHelper.doToSpringCamelContexts(context, new CamelSpringTestHelper.DoToSpringCamelContextsStrategy() {
+
+                @Override
+                public void execute(String contextName, SpringCamelContext camelContext) throws Exception {
+                    LOGGER.info("Enabling RouteCoverage");
+                    EventNotifier notifier = new RouteCoverageEventNotifier(testClass.getName(), testMethod);
+                    camelContext.addService(notifier, true);
+                    camelContext.getManagementStrategy().addEventNotifier(notifier);
+                }
+            });
+        }
+    }
+
     public static void handleProvidesBreakpoint(ConfigurableApplicationContext context, Class<?> testClass) throws Exception {
         Collection<Method> methods = getAllMethods(testClass);
         final List<Breakpoint> breakpoints = new LinkedList<Breakpoint>();

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
index 546462d..195f57d 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
@@ -30,7 +30,7 @@ public class CamelSpringBootExecutionListener extends AbstractTestExecutionListe
 
     @Override
     public void prepareTestInstance(TestContext testContext) throws Exception {
-        LOG.info("@RunWith(CamelSpringBootJUnit4ClassRunner.class) preparing: {}", testContext.getTestClass());
+        LOG.info("@RunWith(CamelSpringBootRunner.class) preparing: {}", testContext.getTestClass());
 
         Class<?> testClass = testContext.getTestClass();
         // we are customizing the Camel context with
@@ -56,15 +56,20 @@ public class CamelSpringBootExecutionListener extends AbstractTestExecutionListe
 
     @Override
     public void beforeTestMethod(TestContext testContext) throws Exception {
-        LOG.info("@RunWith(CamelSpringBootJUnit4ClassRunner.class) before: {}.{}", testContext.getTestClass(), testContext.getTestMethod().getName());
+        LOG.info("@RunWith(CamelSpringBootRunner.class) before: {}.{}", testContext.getTestClass(), testContext.getTestMethod().getName());
 
         Class<?> testClass = testContext.getTestClass();
+        String testName = testContext.getTestMethod().getName();
+
         ConfigurableApplicationContext context = (ConfigurableApplicationContext) testContext.getApplicationContext();
 
         // mark Camel to be startable again and start Camel
         System.clearProperty("skipStartingCamelContext");
 
-        LOG.info("Initialized CamelSpringBootJUnit4ClassRunner now ready to start CamelContext");
+        // route coverage need to know the test method
+        CamelAnnotationsHandler.handleRouteCoverage(context, testClass, (String) -> testName);
+
+        LOG.info("Initialized CamelSpringBootRunner now ready to start CamelContext");
         CamelAnnotationsHandler.handleCamelContextStartup(context, testClass);
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
index 380fac0..aa76c15 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.test.spring;
 
+import java.lang.reflect.Method;
+
 import org.apache.camel.management.JmxSystemPropertyKeys;
 import org.apache.camel.spring.SpringCamelContext;
 import org.slf4j.Logger;
@@ -77,6 +79,7 @@ public class CamelSpringDelegatingTestContextLoader extends DelegatingSmartConte
         AnnotationConfigUtils.registerAnnotationConfigProcessors((BeanDefinitionRegistry) context);
 
         // Post CamelContext(s) instantiation but pre CamelContext(s) start setup
+        CamelAnnotationsHandler.handleRouteCoverage(context, testClass, (String) -> getTestMethod().getName());
         CamelAnnotationsHandler.handleProvidesBreakpoint(context, testClass);
         CamelAnnotationsHandler.handleShutdownTimeout(context, testClass);
         CamelAnnotationsHandler.handleMockEndpoints(context, testClass);
@@ -119,4 +122,14 @@ public class CamelSpringDelegatingTestContextLoader extends DelegatingSmartConte
         return CamelSpringTestHelper.getTestClass();
     }
 
+    /**
+     * Returns the test method under test.
+     *
+     * @return the method that is being executed
+     * @see CamelSpringTestHelper
+     */
+    protected Method getTestMethod() {
+        return CamelSpringTestHelper.getTestMethod();
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
index b595ce3..434f188 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
@@ -32,6 +32,7 @@ import org.apache.camel.impl.InterceptSendToMockEndpointStrategy;
 import org.apache.camel.management.JmxSystemPropertyKeys;
 import org.apache.camel.spi.Breakpoint;
 import org.apache.camel.spi.Debugger;
+import org.apache.camel.spi.EventNotifier;
 import org.apache.camel.spring.SpringCamelContext;
 import org.apache.camel.test.ExcludingPackageScanClassResolver;
 import org.apache.camel.test.spring.CamelSpringTestHelper.DoToSpringCamelContextsStrategy;
@@ -107,7 +108,7 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
     public ApplicationContext loadContext(String... locations) throws Exception {
         
         Class<?> testClass = getTestClass();
-        
+
         if (LOG.isDebugEnabled()) {
             LOG.debug("Loading ApplicationContext for locations [" + StringUtils.arrayToCommaDelimitedString(locations) + "].");
         }
@@ -153,6 +154,7 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
         SpringCamelContext.setNoStart(false);
         
         // Post CamelContext(s) instantiation but pre CamelContext(s) start setup
+        handleRouteCoverage(context, testClass);
         handleProvidesBreakpoint(context, testClass);
         handleShutdownTimeout(context, testClass);
         handleMockEndpoints(context, testClass);
@@ -204,7 +206,6 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
         
         if (mergedConfig != null) {
             parentContext = mergedConfig.getParentApplicationContext();
-
         }
         
         if (testClass.isAnnotationPresent(ExcludeRoutes.class)) {
@@ -258,7 +259,7 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
      */
     protected void handleDisableJmx(GenericApplicationContext context, Class<?> testClass) {
         CamelSpringTestHelper.setOriginalJmxDisabledValue(System.getProperty(JmxSystemPropertyKeys.DISABLED));
-        
+
         if (testClass.isAnnotationPresent(DisableJmx.class)) {
             if (testClass.getAnnotation(DisableJmx.class).value()) {
                 LOG.info("Disabling Camel JMX globally as DisableJmx annotation was found and disableJmx is set to true.");
@@ -267,12 +268,36 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
                 LOG.info("Enabling Camel JMX as DisableJmx annotation was found and disableJmx is set to false.");
                 System.clearProperty(JmxSystemPropertyKeys.DISABLED);
             }
-        } else {
+        } else if (!testClass.isAnnotationPresent(EnableRouteCoverage.class)) {
+            // route coverage need JMX so do not disable it by default
             LOG.info("Disabling Camel JMX globally for tests by default.  Use the DisableJMX annotation to override the default setting.");
             System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
         }
     }
-    
+
+    /**
+     * Handles disabling of JMX on Camel contexts based on {@link DisableJmx}.
+     *
+     * @param context the initialized Spring context
+     * @param testClass the test class being executed
+     */
+    private void handleRouteCoverage(GenericApplicationContext context, Class<?> testClass) throws Exception {
+        if (testClass.isAnnotationPresent(EnableRouteCoverage.class)) {
+            System.setProperty("CamelTestRouteCoverage", "true");
+
+            CamelSpringTestHelper.doToSpringCamelContexts(context, new DoToSpringCamelContextsStrategy() {
+
+                @Override
+                public void execute(String contextName, SpringCamelContext camelContext) throws Exception {
+                    LOG.info("Enabling RouteCoverage");
+                    EventNotifier notifier = new RouteCoverageEventNotifier(testClass.getName(), (String) -> getTestMethod().getName());
+                    camelContext.addService(notifier, true);
+                    camelContext.getManagementStrategy().addEventNotifier(notifier);
+                }
+            });
+        }
+    }
+
     /**
      * Handles the processing of the {@link ProvidesBreakpoint} annotation on a test class.  Exists here
      * as it is needed in 
@@ -504,4 +529,14 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
     protected Class<?> getTestClass() {
         return CamelSpringTestHelper.getTestClass();
     }
+
+    /**
+     * Returns the test method under test.
+     *
+     * @return the method that is being executed
+     * @see CamelSpringTestHelper
+     */
+    protected Method getTestMethod() {
+        return CamelSpringTestHelper.getTestMethod();
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
index d8533d1..100b998 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
@@ -45,5 +45,6 @@ public class CamelSpringTestContextLoaderTestExecutionListener extends AbstractT
     @Override
     public void prepareTestInstance(TestContext testContext) throws Exception {
         CamelSpringTestHelper.setTestClass(testContext.getTestClass());
-    }    
+        CamelSpringTestHelper.setTestContext(testContext);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
index aa75c0e..1c426b4 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
@@ -27,6 +27,7 @@ import java.util.Set;
 import org.apache.camel.spring.SpringCamelContext;
 
 import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.TestContext;
 
 /**
  * Helper that provides state information across the levels of Spring Test that do not expose the
@@ -41,7 +42,8 @@ public final class CamelSpringTestHelper {
     
     private static ThreadLocal<String> originalJmxDisabledValue = new ThreadLocal<String>();
     private static ThreadLocal<Class<?>> testClazz = new ThreadLocal<Class<?>>();
-    
+    private static ThreadLocal<TestContext> testContext = new ThreadLocal<TestContext>();
+
     private CamelSpringTestHelper() {
     }
     
@@ -60,7 +62,15 @@ public final class CamelSpringTestHelper {
     public static void setTestClass(Class<?> testClass) {
         testClazz.set(testClass);
     }
-    
+
+    public static Method getTestMethod() {
+        return testContext.get().getTestMethod();
+    }
+
+    public static void setTestContext(TestContext context) {
+        testContext.set(context);
+    }
+
     /**
      * Returns all methods defined in {@code clazz} and its superclasses/interfaces.
      */

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/EnableRouteCoverage.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/EnableRouteCoverage.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/EnableRouteCoverage.java
new file mode 100644
index 0000000..13c8514
--- /dev/null
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/EnableRouteCoverage.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.test.spring;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Enables dumping route coverage statistic.
+ * The route coverage status is written as xml files in the <tt>target/camel-route-coverage</tt> directory after the test has finished.
+ * <p/>
+ * This allows tooling or manual inspection of the stats, so you can generate a route trace diagram of which EIPs
+ * have been in use and which have not. Similar concepts as a code coverage report.
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface EnableRouteCoverage {
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java
new file mode 100644
index 0000000..17b7064
--- /dev/null
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java
@@ -0,0 +1,98 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.test.spring;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.EventObject;
+import java.util.function.Function;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.api.management.mbean.ManagedCamelContextMBean;
+import org.apache.camel.management.event.CamelContextStoppingEvent;
+import org.apache.camel.support.EventNotifierSupport;
+import org.apache.camel.util.IOHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RouteCoverageEventNotifier extends EventNotifierSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RouteCoverageEventNotifier.class);
+
+    private final String testClassName;
+    private final Function testMethodName;
+
+    public RouteCoverageEventNotifier(String testClassName, Function testMethodName) {
+        this.testClassName = testClassName;
+        this.testMethodName = testMethodName;
+        setIgnoreCamelContextEvents(false);
+        setIgnoreExchangeEvents(true);
+    }
+
+    @Override
+    public boolean isEnabled(EventObject event) {
+        return event instanceof CamelContextStoppingEvent;
+    }
+
+    @Override
+    public void notify(EventObject event) throws Exception {
+        CamelContext context = ((CamelContextStoppingEvent) event).getContext();
+        try {
+            String className = this.getClass().getSimpleName();
+            String dir = "target/camel-route-coverage";
+            String testName = (String) testMethodName.apply(this);
+            String name = className + "-" + testName + ".xml";
+
+            ManagedCamelContextMBean managedCamelContext = context.getManagedCamelContext();
+            if (managedCamelContext == null) {
+                LOG.warn("Cannot dump route coverage to file as JMX is not enabled. Override useJmx() method to enable JMX in the unit test classes.");
+            } else {
+                String xml = managedCamelContext.dumpRoutesCoverageAsXml();
+                String combined = "<camelRouteCoverage>\n" + gatherTestDetailsAsXml(testName) + xml + "\n</camelRouteCoverage>";
+
+                File file = new File(dir);
+                // ensure dir exists
+                file.mkdirs();
+                file = new File(dir, name);
+
+                LOG.info("Dumping route coverage to file: " + file);
+                InputStream is = new ByteArrayInputStream(combined.getBytes());
+                OutputStream os = new FileOutputStream(file, false);
+                IOHelper.copyAndCloseInput(is, os);
+                IOHelper.close(os);
+            }
+        } catch (Exception e) {
+            LOG.warn("Error during dumping route coverage statistic. This exception is ignored.", e);
+        }
+    }
+
+    /**
+     * Gathers test details as xml
+     */
+    private String gatherTestDetailsAsXml(String testName) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<test>\n");
+        sb.append("  <class>").append(testClassName).append("</class>\n");
+        sb.append("  <method>").append(testName).append("</method>\n");
+        sb.append("</test>\n");
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java
new file mode 100644
index 0000000..9374d2f
--- /dev/null
+++ b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java
@@ -0,0 +1,55 @@
+/**
+ * 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.test.spring;
+
+import java.io.File;
+
+import org.apache.camel.management.ManagedManagementStrategy;
+import org.apache.camel.test.junit4.TestSupport;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@EnableRouteCoverage
+public class CamelSpringRouteProcessorDumpRouteCoverageTest extends CamelSpringRunnerPlainTest {
+
+    @BeforeClass
+    public static void prepareFiles() throws Exception {
+        TestSupport.deleteDirectory("target/camel-route-coverage");
+    }
+
+    @Test
+    public void testJmx() throws Exception {
+        // JMX is enabled with route coverage
+        assertEquals(ManagedManagementStrategy.class, camelContext.getManagementStrategy().getClass());
+    }
+
+    @Override
+    public void testRouteCoverage() throws Exception{
+        camelContext.stop();
+        camelContext2.stop();
+
+        // there should be files
+        String[] names = new File("target/camel-route-coverage").list();
+        assertNotNull(names);
+        assertTrue(names.length > 0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
index 17fa2d6..39b2d88 100644
--- a/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
+++ b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
@@ -118,6 +118,11 @@ public class CamelSpringRunnerPlainTest {
         assertNull(camelContext2.getDebugger());
     }
 
+    @Test
+    public void testRouteCoverage() throws Exception {
+        // noop
+    }
+
 }
 // end::example[]
 // END SNIPPET: e1

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
----------------------------------------------------------------------
diff --git a/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java b/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
index 632b8b0..2206822 100644
--- a/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
+++ b/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
@@ -391,7 +391,8 @@ public abstract class CamelTestSupport extends TestSupport {
         log.info("Took: " + TimeUtils.printDuration(time) + " (" + time + " millis)");
 
         // if we should dump route stats, then write that to a file
-        if (isDumpRouteCoverage()) {
+        boolean coverage = System.getProperty("CamelTestRouteCoverage", "false").equalsIgnoreCase("true") || isDumpRouteCoverage();
+        if (coverage) {
             String className = this.getClass().getSimpleName();
             String dir = "target/camel-route-coverage";
             String name = className + "-" + getTestMethodName() + ".xml";
@@ -485,7 +486,6 @@ public abstract class CamelTestSupport extends TestSupport {
 
         builder.append(routesSummary);
         log.info(builder.toString());
-
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/pom.xml
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/pom.xml b/examples/camel-example-spring-boot-xml/pom.xml
new file mode 100644
index 0000000..3501809
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/pom.xml
@@ -0,0 +1,151 @@
+<?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.example</groupId>
+    <artifactId>examples</artifactId>
+    <version>2.20.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>camel-example-spring-boot-xml</artifactId>
+  <name>Camel :: Example :: Spring Boot XML</name>
+  <description>An example showing how to work with Camel routes in XML files and Spring Boot</description>
+
+  <properties>
+    <category>Beginner</category>
+
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <spring.boot-version>${spring-boot-version}</spring.boot-version>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <!-- Spring Boot BOM -->
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring.boot-version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <!-- Camel BOM -->
+      <dependency>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-spring-boot-dependencies</artifactId>
+        <version>${project.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+
+    <!-- Spring Boot -->
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-undertow</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-actuator</artifactId>
+    </dependency>
+
+    <!-- Camel -->
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-spring-boot-starter</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-stream-starter</artifactId>
+    </dependency>
+
+    <!-- test -->
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-test-spring</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <version>${spring-boot-version}</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>repackage</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <!-- allows to fail if not all routes are fully covered during testing -->
+<!--
+        <configuration>
+          <failOnError>true</failOnError>
+        </configuration>
+-->
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>jdk9-build</id>
+      <activation>
+        <jdk>9</jdk>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <argLine>--add-modules java.xml.bind --add-opens java.base/java.lang=ALL-UNNAMED</argLine>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/readme.adoc
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/readme.adoc b/examples/camel-example-spring-boot-xml/readme.adoc
new file mode 100644
index 0000000..72475b2
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/readme.adoc
@@ -0,0 +1,41 @@
+# Camel Example Spring Boot
+
+This example shows how to work with a simple Apache Camel application using Spring Boot.
+
+The example generates messages using timer trigger, writes them to standard output.
+
+## Camel routes
+
+The Camel route is located in the `SampleCamelRouter` class. In this class the route
+starts from a timer, that triggers every 2nd second and calls a Spring Bean `SampleBean`
+which returns a message, that is routed to a stream endpoint which writes to standard output.
+
+## Using Camel components
+
+Apache Camel provides 200+ components which you can use to integrate and route messages between many systems
+and data formats. To use any of these Camel components, add the component as a dependency to your project.
+
+## How to run
+
+You can run this example using
+
+    mvn spring-boot:run
+
+## To get info about the routes
+
+To show a summary of all the routes
+
+----
+curl -XGET -s http://localhost:8080/camel/routes
+----
+
+To show detailed information for a specific route
+
+----
+curl -XGET -s http://localhost:8080/camel/routes/{id}/info
+----
+
+
+## More information
+
+You can find more information about Apache Camel at the website: http://camel.apache.org/

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleBean.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleBean.java b/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleBean.java
new file mode 100644
index 0000000..b60ef69
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleBean.java
@@ -0,0 +1,38 @@
+/**
+ * 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 sample.camel;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * A bean that returns a message when you call the {@link #saySomething()} method.
+ * <p/>
+ * Uses <tt>@Component("myBean")</tt> to register this bean with the name <tt>myBean</tt>
+ * that we use in the Camel route to lookup this bean.
+ */
+@Component("myBean")
+public class SampleBean {
+
+    @Value("${greeting}")
+    private String say;
+
+    public String saySomething() {
+        return say;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleCamelApplication.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleCamelApplication.java b/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleCamelApplication.java
new file mode 100644
index 0000000..a678dae
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/src/main/java/sample/camel/SampleCamelApplication.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 sample.camel;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ImportResource;
+
+//CHECKSTYLE:OFF
+/**
+ * A sample Spring Boot application that starts the Camel routes.
+ */
+@SpringBootApplication
+// load the spring xml file from classpath
+@ImportResource("classpath:my-camel.xml")
+public class SampleCamelApplication {
+
+    /**
+     * A main method to start this application.
+     */
+    public static void main(String[] args) {
+        SpringApplication.run(SampleCamelApplication.class, args);
+    }
+
+}
+//CHECKSTYLE:ON

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/src/main/resources/application.properties
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/src/main/resources/application.properties b/examples/camel-example-spring-boot-xml/src/main/resources/application.properties
new file mode 100644
index 0000000..75a10de
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/src/main/resources/application.properties
@@ -0,0 +1,46 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+# the name of Camel
+camel.springboot.name = SampleCamel
+
+# to automatic shutdown the JVM after a period of time
+#camel.springboot.duration-max-seconds=60
+#camel.springboot.duration-max-messages=100
+
+# add for example: &repeatCount=5 to the timer endpoint to make Camel idle
+#camel.springboot.duration-max-idle-seconds=15
+
+# properties used in the Camel route and beans
+# --------------------------------------------
+
+# what to say
+greeting = Hello World
+
+# how often to trigger the timer
+timer.period = 2000
+
+# all access to actuator endpoints without security
+management.security.enabled = false
+# turn on actuator health check
+endpoints.health.enabled = true
+
+# to configure logging levels
+#logging.level.org.springframework = INFO
+#logging.level.org.apache.camel.spring.boot = INFO
+#logging.level.org.apache.camel.impl = DEBUG
+#logging.level.sample.camel = DEBUG

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/src/main/resources/my-camel.xml
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/src/main/resources/my-camel.xml b/examples/camel-example-spring-boot-xml/src/main/resources/my-camel.xml
new file mode 100644
index 0000000..3fc6e0f
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/src/main/resources/my-camel.xml
@@ -0,0 +1,40 @@
+<?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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+         http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
+
+  <camelContext id="SampleCamel" xmlns="http://camel.apache.org/schema/spring">
+    <route id="hello">
+      <from uri="timer:hello?period={{timer.period}}"/>
+      <transform>
+        <method ref="myBean" method="saySomething"/>
+      </transform>
+      <filter>
+        <simple>${body} contains 'foo'</simple>
+        <to uri="log:foo"/>
+      </filter>
+      <to uri="stream:out"/>
+    </route>
+  </camelContext>
+
+</beans>

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/FooApplicationTest.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/FooApplicationTest.java b/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/FooApplicationTest.java
new file mode 100644
index 0000000..5a71fc9
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/FooApplicationTest.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 sample.camel;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.spring.CamelSpringBootRunner;
+import org.apache.camel.test.spring.EnableRouteCoverage;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(CamelSpringBootRunner.class)
+@SpringBootTest(classes = SampleCamelApplication.class,
+    properties = "greeting = Hello foo")
+@EnableRouteCoverage
+@Ignore // enable me to run this test as well so we can cover testing the route completely
+public class FooApplicationTest {
+
+    @Autowired
+    private CamelContext camelContext;
+
+    @Test
+    public void shouldSayFoo() throws Exception {
+        // we expect that one or more messages is automatic done by the Camel
+        // route as it uses a timer to trigger
+        NotifyBuilder notify = new NotifyBuilder(camelContext).whenDone(1).create();
+
+        assertTrue(notify.matches(10, TimeUnit.SECONDS));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/SampleCamelApplicationTest.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/SampleCamelApplicationTest.java b/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/SampleCamelApplicationTest.java
new file mode 100644
index 0000000..f4c2fc5
--- /dev/null
+++ b/examples/camel-example-spring-boot-xml/src/test/java/sample/camel/SampleCamelApplicationTest.java
@@ -0,0 +1,49 @@
+/**
+ * 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 sample.camel;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.spring.CamelSpringBootRunner;
+import org.apache.camel.test.spring.EnableRouteCoverage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(CamelSpringBootRunner.class)
+@SpringBootTest(classes = SampleCamelApplication.class)
+@EnableRouteCoverage
+public class SampleCamelApplicationTest {
+
+    @Autowired
+    private CamelContext camelContext;
+
+    @Test
+    public void shouldProduceMessages() throws Exception {
+        // we expect that one or more messages is automatic done by the Camel
+        // route as it uses a timer to trigger
+        NotifyBuilder notify = new NotifyBuilder(camelContext).whenDone(1).create();
+
+        assertTrue(notify.matches(10, TimeUnit.SECONDS));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot/pom.xml
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot/pom.xml b/examples/camel-example-spring-boot/pom.xml
index dbf3333..3467c2a 100644
--- a/examples/camel-example-spring-boot/pom.xml
+++ b/examples/camel-example-spring-boot/pom.xml
@@ -115,6 +115,18 @@
           </execution>
         </executions>
       </plugin>
+
+      <plugin>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <!-- allows to fail if not all routes are fully covered during testing -->
+<!--
+        <configuration>
+          <failOnError>true</failOnError>
+        </configuration>
+-->
+      </plugin>
     </plugins>
   </build>
 

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java b/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
index 4e0422e..4b11938 100644
--- a/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
+++ b/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
@@ -29,8 +29,11 @@ public class SampleCamelRouter extends RouteBuilder {
 
     @Override
     public void configure() throws Exception {
-        from("timer:hello?period={{timer.period}}")
-                .transform(method("myBean", "saySomething"))
+        from("timer:hello?period={{timer.period}}").routeId("hello")
+                .transform().method("myBean", "saySomething")
+                .filter(simple("${body} contains 'foo'"))
+                    .to("log:foo")
+                .end()
                 .to("stream:out");
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot/src/test/java/sample/camel/FooApplicationTest.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot/src/test/java/sample/camel/FooApplicationTest.java b/examples/camel-example-spring-boot/src/test/java/sample/camel/FooApplicationTest.java
new file mode 100644
index 0000000..45f2076
--- /dev/null
+++ b/examples/camel-example-spring-boot/src/test/java/sample/camel/FooApplicationTest.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 sample.camel;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.test.spring.CamelSpringBootRunner;
+import org.apache.camel.test.spring.EnableRouteCoverage;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(CamelSpringBootRunner.class)
+@SpringBootTest(classes = SampleCamelApplication.class,
+    properties = "greeting = Hello foo")
+@EnableRouteCoverage
+//@Ignore // enable me to run this test as well so we can cover testing the route completely
+public class FooApplicationTest {
+
+    @Autowired
+    private CamelContext camelContext;
+
+    @Test
+    public void shouldSayFoo() throws Exception {
+        // we expect that one or more messages is automatic done by the Camel
+        // route as it uses a timer to trigger
+        NotifyBuilder notify = new NotifyBuilder(camelContext).whenDone(1).create();
+
+        assertTrue(notify.matches(10, TimeUnit.SECONDS));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/camel-example-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java b/examples/camel-example-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java
index 7d53276..f4c2fc5 100644
--- a/examples/camel-example-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java
+++ b/examples/camel-example-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java
@@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.camel.CamelContext;
 import org.apache.camel.builder.NotifyBuilder;
 import org.apache.camel.test.spring.CamelSpringBootRunner;
+import org.apache.camel.test.spring.EnableRouteCoverage;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -30,6 +31,7 @@ import static org.junit.Assert.assertTrue;
 
 @RunWith(CamelSpringBootRunner.class)
 @SpringBootTest(classes = SampleCamelApplication.class)
+@EnableRouteCoverage
 public class SampleCamelApplicationTest {
 
     @Autowired

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/examples/pom.xml
----------------------------------------------------------------------
diff --git a/examples/pom.xml b/examples/pom.xml
index f8cdc10..717f593 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -108,6 +108,7 @@
     <module>camel-example-spring-boot-rest-swagger</module>
     <module>camel-example-spring-boot-servicecall</module>
     <module>camel-example-spring-boot-supervising-route-controller</module>
+    <module>camel-example-spring-boot-xml</module>
     <module>camel-example-spring-cloud-servicecall</module>
     <module>camel-example-spring-javaconfig</module>
     <module>camel-example-spring-jms</module>

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/tooling/camel-route-parser/pom.xml
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/pom.xml b/tooling/camel-route-parser/pom.xml
index 0f65e3e..4cfc2bd 100644
--- a/tooling/camel-route-parser/pom.xml
+++ b/tooling/camel-route-parser/pom.xml
@@ -42,6 +42,13 @@
       <scope>provided</scope>
     </dependency>
 
+    <!-- the catalog has details the parser needs to parse the Camel Java DSL -->
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-catalog</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
     <!-- only test scopes for camel as we have no runtime dependency on camel -->
     <dependency>
       <groupId>org.apache.camel</groupId>

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java
index d38e1d6..96e54b2 100644
--- a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java
+++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java
@@ -20,10 +20,13 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+import org.apache.camel.parser.helper.CamelJavaTreeParserHelper;
 import org.apache.camel.parser.helper.CamelJavaParserHelper;
 import org.apache.camel.parser.model.CamelEndpointDetails;
+import org.apache.camel.parser.model.CamelNodeDetails;
 import org.apache.camel.parser.model.CamelRouteDetails;
 import org.apache.camel.parser.model.CamelSimpleExpressionDetails;
 import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.ASTNode;
@@ -48,6 +51,42 @@ public final class RouteBuilderParser {
     }
 
     /**
+     * Parses the java source class and build a route model (tree) of the discovered routes in the java source class.
+     *
+     * @param clazz                   the java source class
+     * @param baseDir                 the base of the source code
+     * @param fullyQualifiedFileName  the fully qualified source code file name
+     * @return a list of route model (tree) of each discovered route
+     */
+    public static List<CamelNodeDetails> parseRouteBuilderTree(JavaClassSource clazz, String baseDir, String fullyQualifiedFileName,
+                                                               boolean includeInlinedRouteBuilders) {
+
+        List<MethodSource<JavaClassSource>> methods = new ArrayList<>();
+        MethodSource<JavaClassSource> method = CamelJavaParserHelper.findConfigureMethod(clazz);
+        if (method != null) {
+            methods.add(method);
+        }
+        if (includeInlinedRouteBuilders) {
+            List<MethodSource<JavaClassSource>> inlinedMethods = CamelJavaParserHelper.findInlinedConfigureMethods(clazz);
+            if (!inlinedMethods.isEmpty()) {
+                methods.addAll(inlinedMethods);
+            }
+        }
+
+        CamelJavaTreeParserHelper parser = new CamelJavaTreeParserHelper();
+        List<CamelNodeDetails> list = new ArrayList<>();
+        for (MethodSource<JavaClassSource> configureMethod : methods) {
+            // there may be multiple route builder configure methods
+            List<CamelNodeDetails> details = parser.parseCamelRouteTree(clazz, baseDir, fullyQualifiedFileName, configureMethod);
+            list.addAll(details);
+        }
+        // we end up parsing bottom->up so reverse list
+        Collections.reverse(list);
+
+        return list;
+    }
+
+    /**
      * Parses the java source class to discover Camel endpoints.
      *
      * @param clazz                   the java source class

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java
index 364cf9a..5512837 100644
--- a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java
+++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java
@@ -17,8 +17,12 @@
 package org.apache.camel.parser;
 
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.camel.parser.helper.CamelXmlTreeParserHelper;
+import org.apache.camel.parser.model.CamelNodeDetails;
+import org.apache.camel.parser.model.CamelNodeDetailsFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
@@ -44,6 +48,59 @@ public final class XmlRouteParser {
     }
 
     /**
+     * Parses the XML file and build a route model (tree) of the discovered routes in the XML source file.
+     *
+     * @param xml                     the xml file as input stream
+     * @param baseDir                 the base of the source code
+     * @param fullyQualifiedFileName  the fully qualified source code file name
+     * @return a list of route model (tree) of each discovered route
+     */
+    public static List<CamelNodeDetails> parseXmlRouteTree(InputStream xml, String baseDir, String fullyQualifiedFileName) {
+        List<CamelNodeDetails> answer = new ArrayList<>();
+
+        // try parse it as dom
+        Document dom = null;
+        try {
+            dom = XmlLineNumberParser.parseXml(xml);
+        } catch (Exception e) {
+            // ignore as the xml file may not be valid at this point
+        }
+        if (dom != null) {
+
+            // find any from which is the start of the route
+            CamelNodeDetailsFactory nodeFactory = CamelNodeDetailsFactory.newInstance();
+
+            CamelXmlTreeParserHelper parser = new CamelXmlTreeParserHelper();
+
+            List<Node> routes = CamelXmlHelper.findAllRoutes(dom);
+            for (Node route : routes) {
+                // parse each route and build
+                String routeId = getSafeAttribute(route, "id");
+                String lineNumber = (String) route.getUserData(XmlLineNumberParser.LINE_NUMBER);
+                String lineNumberEnd = (String) route.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
+
+                // we only want the relative dir name from the resource directory, eg META-INF/spring/foo.xml
+                String fileName = fullyQualifiedFileName;
+                if (fileName.startsWith(baseDir)) {
+                    fileName = fileName.substring(baseDir.length() + 1);
+                }
+
+                CamelNodeDetails node = nodeFactory.newNode(null, "route");
+                node.setRouteId(routeId);
+                node.setFileName(fileName);
+                node.setLineNumber(lineNumber);
+                node.setLineNumberEnd(lineNumberEnd);
+
+                // parse the route and gather all its EIPs
+                List<CamelNodeDetails> tree = parser.parseCamelRouteTree(route, routeId, node, baseDir, fullyQualifiedFileName);
+                answer.addAll(tree);
+            }
+        }
+
+        return answer;
+    }
+
+    /**
      * Parses the XML source to discover Camel endpoints.
      *
      * @param xml                     the xml file as input stream

http://git-wip-us.apache.org/repos/asf/camel/blob/f015f7b0/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
new file mode 100644
index 0000000..a1f4807
--- /dev/null
+++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
@@ -0,0 +1,70 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.parser.graph;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+import org.apache.camel.parser.model.CamelNodeDetails;
+
+/**
+ * @deprecated  experiment to render a route via Java image
+ */
+@Deprecated
+public class RenderRoute {
+
+    public static void main(String[] args) {
+        RenderRoute render = new RenderRoute();
+        render(null);
+    }
+
+    public static void render(CamelNodeDetails root) {
+        // TODO:
+        try {
+            int width = 200, height = 200;
+
+            // TYPE_INT_ARGB specifies the image format: 8-bit RGBA packed
+            // into integer pixels
+//            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+            Graphics2D ig2 = bi.createGraphics();
+
+            ig2.drawRect(10, 10, 80, 40);
+            ig2.drawLine(45, 50, 45, 80);
+            ig2.drawRect(10, 80, 80, 40);
+
+            Font font = new Font("Arial", Font.BOLD, 20);
+            ig2.setFont(font);
+            String message = "Apache Camel";
+            FontMetrics fontMetrics = ig2.getFontMetrics();
+            int stringWidth = fontMetrics.stringWidth(message);
+            int stringHeight = fontMetrics.getAscent();
+            ig2.setPaint(Color.black);
+            ig2.drawString(message, (width - stringWidth) / 2, height / 2 + stringHeight / 4);
+
+            ImageIO.write(bi, "PNG", new File("target/route.png"));
+
+        } catch (IOException ie) {
+            ie.printStackTrace();
+        }
+
+    }
+}