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

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

commit aee36d9381b61926c40cf9d3433f9b1aab8a7313
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Fri May 8 14:32:17 2020 +0200

    CAMEL-13535: Camel main - Allow to configure supervising route controller
---
 .../impl/engine/SupervisingRouteController.java    |  19 ++-
 .../MainConfigurationPropertiesConfigurer.java     |  35 +++++
 .../camel-main-configuration-metadata.json         |   7 +
 .../camel/main/DefaultConfigurationConfigurer.java |  37 +++++
 .../camel/main/DefaultConfigurationProperties.java | 165 +++++++++++++++++++++
 .../main/MainSupervisingRouteControllerTest.java   | 110 ++++++++++++++
 .../org/apache/camel/util/backoff/BackOff.java     |  14 +-
 7 files changed, 373 insertions(+), 14 deletions(-)

diff --git 
a/core/camel-base/src/main/java/org/apache/camel/impl/engine/SupervisingRouteController.java
 
b/core/camel-base/src/main/java/org/apache/camel/impl/engine/SupervisingRouteController.java
index 890960a..ea990ec 100644
--- 
a/core/camel-base/src/main/java/org/apache/camel/impl/engine/SupervisingRouteController.java
+++ 
b/core/camel-base/src/main/java/org/apache/camel/impl/engine/SupervisingRouteController.java
@@ -63,6 +63,9 @@ import org.slf4j.LoggerFactory;
  * settings for backoff between restarting routes.
  */
 public class SupervisingRouteController extends DefaultRouteController {
+
+    // TODO: Make SPI interface so we can separate this more nicely
+
     private static final Logger LOGGER = 
LoggerFactory.getLogger(SupervisingRouteController.class);
     private final Object lock;
     private final AtomicBoolean contextStarted;
@@ -92,7 +95,7 @@ public class SupervisingRouteController extends 
DefaultRouteController {
             this.listener = new CamelContextStartupListener();
             this.listener.start();
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            throw RuntimeCamelException.wrapRuntimeException(e);
         }
     }
 
@@ -169,6 +172,7 @@ public class SupervisingRouteController extends 
DefaultRouteController {
     /**
      * Add a filter used to determine the routes to supervise.
      */
+    @Deprecated
     public void addFilter(Filter filter) {
         this.filters.add(filter);
     }
@@ -176,11 +180,13 @@ public class SupervisingRouteController extends 
DefaultRouteController {
     /**
      * Sets the filters user to determine the routes to supervise.
      */
+    @Deprecated
     public void setFilters(Collection<Filter> filters) {
         this.filters.clear();
         this.filters.addAll(filters);
     }
 
+    @Deprecated
     public Collection<Filter> getFilters() {
         return Collections.unmodifiableList(filters);
     }
@@ -432,7 +438,8 @@ public class SupervisingRouteController extends 
DefaultRouteController {
 
                     BackOffTimer.Task task = timer.schedule(backOff, context 
-> {
                         try {
-                            logger.info("Try to restart route: {}", r.getId());
+                            long attempt = 
getBackOffContext(r.getId()).map(BackOffTimer.Task::getCurrentAttempts).orElse(0L);
+                            logger.info("Restarting route: {} attempt: {}", 
r.getId(), attempt);
 
                             doStartRoute(r, false, rx -> 
SupervisingRouteController.super.startRoute(rx.getId()));
                             return false;
@@ -453,7 +460,9 @@ public class SupervisingRouteController extends 
DefaultRouteController {
                                 final boolean stopped = status.isStopped() || 
status.isStopping();
 
                                 if (backOffTask != null && 
backOffTask.getStatus() == BackOffTimer.Task.Status.Exhausted && stopped) {
-                                    LOGGER.info("Back-off for route {} is 
exhausted, no more attempts will be made and stop supervising it", 
route.getId());
+                                    LOGGER.warn("Restarting route: {} is 
exhausted after {} attempts. No more attempts will be made"
+                                                    + " and the route is no 
longer supervised by this route controller and remains as stopped.",
+                                            route.getId(), 
backOffTask.getCurrentAttempts() - 1);
                                     r.get().setRouteController(null);
                                 }
                             }
@@ -601,7 +610,7 @@ public class SupervisingRouteController extends 
DefaultRouteController {
                 holder.get().setAutoStartup(false);
 
                 if (contextStarted.get()) {
-                    LOGGER.info("Context is already started: attempt to start 
route {}", route.getId());
+                    LOGGER.debug("Context is already started: attempt to start 
route {}", route.getId());
 
                     // Eventually delay the startup of the route a later time
                     if (initialDelay.toMillis() > 0) {
@@ -611,7 +620,7 @@ public class SupervisingRouteController extends 
DefaultRouteController {
                         startRoute(holder);
                     }
                 } else {
-                    LOGGER.info("Context is not yet started: defer route {} 
start", holder.getId());
+                    LOGGER.debug("Context is not yet started: defer route {} 
start", holder.getId());
                 }
             }
         }
diff --git 
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
 
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
index a06e794..f167ac7 100644
--- 
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
+++ 
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
@@ -105,6 +105,20 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "PackageScanRouteBuilders": 
target.setPackageScanRouteBuilders(property(camelContext, 
java.lang.String.class, value)); return true;
         case "producertemplatecachesize":
         case "ProducerTemplateCacheSize": 
target.setProducerTemplateCacheSize(property(camelContext, int.class, value)); 
return true;
+        case "routecontrollerbackoffdelay":
+        case "RouteControllerBackOffDelay": 
target.setRouteControllerBackOffDelay(property(camelContext, long.class, 
value)); return true;
+        case "routecontrollerbackoffmaxattempts":
+        case "RouteControllerBackOffMaxAttempts": 
target.setRouteControllerBackOffMaxAttempts(property(camelContext, long.class, 
value)); return true;
+        case "routecontrollerbackoffmaxdelay":
+        case "RouteControllerBackOffMaxDelay": 
target.setRouteControllerBackOffMaxDelay(property(camelContext, long.class, 
value)); return true;
+        case "routecontrollerbackoffmaxelapsedtime":
+        case "RouteControllerBackOffMaxElapsedTime": 
target.setRouteControllerBackOffMaxElapsedTime(property(camelContext, 
long.class, value)); return true;
+        case "routecontrollerbackoffmultiplier":
+        case "RouteControllerBackOffMultiplier": 
target.setRouteControllerBackOffMultiplier(property(camelContext, double.class, 
value)); return true;
+        case "routecontrollerenabled":
+        case "RouteControllerEnabled": 
target.setRouteControllerEnabled(property(camelContext, boolean.class, value)); 
return true;
+        case "routecontrollerinitialdelay":
+        case "RouteControllerInitialDelay": 
target.setRouteControllerInitialDelay(property(camelContext, long.class, 
value)); return true;
         case "routefilterexcludepattern":
         case "RouteFilterExcludePattern": 
target.setRouteFilterExcludePattern(property(camelContext, 
java.lang.String.class, value)); return true;
         case "routefilterincludepattern":
@@ -211,6 +225,13 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         answer.put("Name", java.lang.String.class);
         answer.put("PackageScanRouteBuilders", java.lang.String.class);
         answer.put("ProducerTemplateCacheSize", int.class);
+        answer.put("RouteControllerBackOffDelay", long.class);
+        answer.put("RouteControllerBackOffMaxAttempts", long.class);
+        answer.put("RouteControllerBackOffMaxDelay", long.class);
+        answer.put("RouteControllerBackOffMaxElapsedTime", long.class);
+        answer.put("RouteControllerBackOffMultiplier", double.class);
+        answer.put("RouteControllerEnabled", boolean.class);
+        answer.put("RouteControllerInitialDelay", long.class);
         answer.put("RouteFilterExcludePattern", java.lang.String.class);
         answer.put("RouteFilterIncludePattern", java.lang.String.class);
         answer.put("RoutesBuilderClasses", java.lang.String.class);
@@ -332,6 +353,20 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "PackageScanRouteBuilders": return 
target.getPackageScanRouteBuilders();
         case "producertemplatecachesize":
         case "ProducerTemplateCacheSize": return 
target.getProducerTemplateCacheSize();
+        case "routecontrollerbackoffdelay":
+        case "RouteControllerBackOffDelay": return 
target.getRouteControllerBackOffDelay();
+        case "routecontrollerbackoffmaxattempts":
+        case "RouteControllerBackOffMaxAttempts": return 
target.getRouteControllerBackOffMaxAttempts();
+        case "routecontrollerbackoffmaxdelay":
+        case "RouteControllerBackOffMaxDelay": return 
target.getRouteControllerBackOffMaxDelay();
+        case "routecontrollerbackoffmaxelapsedtime":
+        case "RouteControllerBackOffMaxElapsedTime": return 
target.getRouteControllerBackOffMaxElapsedTime();
+        case "routecontrollerbackoffmultiplier":
+        case "RouteControllerBackOffMultiplier": return 
target.getRouteControllerBackOffMultiplier();
+        case "routecontrollerenabled":
+        case "RouteControllerEnabled": return 
target.isRouteControllerEnabled();
+        case "routecontrollerinitialdelay":
+        case "RouteControllerInitialDelay": return 
target.getRouteControllerInitialDelay();
         case "routefilterexcludepattern":
         case "RouteFilterExcludePattern": return 
target.getRouteFilterExcludePattern();
         case "routefilterincludepattern":
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index ab41e7c..5727428 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -50,6 +50,13 @@
     { "name": "camel.main.name", "description": "Sets the name of the 
CamelContext.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string", 
"javaType": "java.lang.String" },
     { "name": "camel.main.packageScanRouteBuilders", "description": "Sets 
package names for scanning for org.apache.camel.builder.RouteBuilder classes as 
candidates to be included. If you are using Spring Boot then its instead 
recommended to use Spring Boots component scanning and annotate your route 
builder classes with Component. In other words only use this for Camel Main in 
standalone mode.", "sourceType": 
"org.apache.camel.main.MainConfigurationProperties", "type": "string", "javaTy 
[...]
     { "name": "camel.main.producerTemplateCacheSize", "description": "Producer 
template endpoints cache size.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 1000 },
+    { "name": "camel.main.routeControllerBackOffDelay", "description": 
"Backoff delay in millis when restarting a route that failed to startup.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"integer", "javaType": "long" },
+    { "name": "camel.main.routeControllerBackOffMaxAttempts", "description": 
"Backoff maximum number of attempts to restart a route that failed to startup. 
When this threshold has been exceeded then the controller will give up 
attempting to restart the route, and the route will remain as stopped.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"integer", "javaType": "long" },
+    { "name": "camel.main.routeControllerBackOffMaxDelay", "description": 
"Backoff maximum delay in millis when restarting a route that failed to 
startup.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "long" },
+    { "name": "camel.main.routeControllerBackOffMaxElapsedTime", 
"description": "Backoff maximum elapsed time in millis, after which the backoff 
should be considered exhausted and no more attempts should be made.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"integer", "javaType": "long" },
+    { "name": "camel.main.routeControllerBackOffMultiplier", "description": 
"Backoff multiplier to use for exponential backoff. This is used to extend the 
delay between restart attempts.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "number", 
"javaType": "double" },
+    { "name": "camel.main.routeControllerEnabled", "description": "To enable 
using supervising route controller which allows Camel to startup and then the 
controller takes care of starting the routes in a safe manner. This can be used 
when you want to startup Camel despite a route may otherwise fail fast during 
startup and cause Camel to fail to startup as well. By delegating the route 
startup to the supervising route controller then its manages the startup using 
a background thread. The [...]
+    { "name": "camel.main.routeControllerInitialDelay", "description": 
"Initial delay in milli seconds before the route controller starts, after 
CamelContext has been started.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "long" },
     { "name": "camel.main.routeFilterExcludePattern", "description": "Used for 
filtering routes routes matching the given pattern, which follows the following 
rules: - Match by route id - Match by route input endpoint uri The matching is 
using exact match, by wildcard and regular expression as documented by 
PatternHelper#matchPattern(String,String) . For example to only include routes 
which starts with foo in their route id's, use: include=foo&#42; And to exclude 
routes which starts from [...]
     { "name": "camel.main.routeFilterIncludePattern", "description": "Used for 
filtering routes routes matching the given pattern, which follows the following 
rules: - Match by route id - Match by route input endpoint uri The matching is 
using exact match, by wildcard and regular expression as documented by 
PatternHelper#matchPattern(String,String) . For example to only include routes 
which starts with foo in their route id's, use: include=foo&#42; And to exclude 
routes which starts from [...]
     { "name": "camel.main.routesBuilderClasses", "description": "Sets classes 
names that implement RoutesBuilder .", "sourceType": 
"org.apache.camel.main.MainConfigurationProperties", "type": "string", 
"javaType": "java.lang.String" },
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index d945450..29a7363 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -31,6 +31,7 @@ import org.apache.camel.cluster.CamelClusterService;
 import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.health.HealthCheckRepository;
 import org.apache.camel.health.HealthCheckService;
+import org.apache.camel.impl.engine.SupervisingRouteController;
 import org.apache.camel.model.Model;
 import org.apache.camel.processor.interceptor.BacklogTracer;
 import org.apache.camel.spi.AsyncProcessorAwaitManager;
@@ -65,6 +66,7 @@ import org.apache.camel.spi.UnitOfWorkFactory;
 import org.apache.camel.spi.UuidGenerator;
 import org.apache.camel.support.jsse.GlobalSSLContextParametersSupplier;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.backoff.BackOff;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -176,6 +178,41 @@ public final class DefaultConfigurationConfigurer {
         if (config.getRouteFilterIncludePattern() != null || 
config.getRouteFilterExcludePattern() != null) {
             
camelContext.getExtension(Model.class).setRouteFilterPattern(config.getRouteFilterIncludePattern(),
 config.getRouteFilterExcludePattern());
         }
+
+        // supervisting route controller
+        if (config.isRouteControllerEnabled()) {
+            SupervisingRouteController src = new SupervisingRouteController();
+            src.setCamelContext(camelContext);
+            if (config.getRouteControllerInitialDelay() > 0) {
+                src.setInitialDelay(config.getRouteControllerInitialDelay());
+            }
+            BackOff.Builder builder = BackOff.builder();
+            boolean flag = false;
+            if (config.getRouteControllerBackOffDelay() > 0) {
+                builder.delay(config.getRouteControllerBackOffDelay());
+                flag = true;
+            }
+            if (config.getRouteControllerBackOffMaxDelay() > 0) {
+                builder.maxDelay(config.getRouteControllerBackOffMaxDelay());
+                flag = true;
+            }
+            if (config.getRouteControllerBackOffMaxAttempts() > 0) {
+                
builder.maxAttempts(config.getRouteControllerBackOffMaxAttempts());
+                flag = true;
+            }
+            if (config.getRouteControllerBackOffMaxElapsedTime() > 0) {
+                
builder.maxElapsedTime(config.getRouteControllerBackOffMaxElapsedTime());
+                flag = true;
+            }
+            if (config.getRouteControllerBackOffMultiplier() > 0) {
+                
builder.multiplier(config.getRouteControllerBackOffMultiplier());
+                flag = true;
+            }
+            if (flag) {
+                src.setDefaultBackOff(builder.build());
+            }
+            camelContext.setRouteController(src);
+        }
     }
 
     /**
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index bd27e5c..0a56ebf 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -81,6 +81,13 @@ public abstract class DefaultConfigurationProperties<T> {
     private String xmlRoutes = "classpath:camel/*.xml";
     private String xmlRests = "classpath:camel-rest/*.xml";
     private boolean lightweight;
+    private boolean routeControllerEnabled;
+    private long routeControllerInitialDelay;
+    private long routeControllerBackOffDelay;
+    private long routeControllerBackOffMaxDelay;
+    private long routeControllerBackOffMaxElapsedTime;
+    private long routeControllerBackOffMaxAttempts;
+    private double routeControllerBackOffMultiplier;
 
     // getter and setters
     // --------------------------------------------------------------
@@ -890,6 +897,95 @@ public abstract class DefaultConfigurationProperties<T> {
         this.lightweight = lightweight;
     }
 
+    public boolean isRouteControllerEnabled() {
+        return routeControllerEnabled;
+    }
+
+    /**
+     * To enable using supervising route controller which allows Camel to 
startup
+     * and then the controller takes care of starting the routes in a safe 
manner.
+     *
+     * This can be used when you want to startup Camel despite a route may 
otherwise
+     * fail fast during startup and cause Camel to fail to startup as well. By 
delegating
+     * the route startup to the supervising route controller then its manages 
the startup
+     * using a background thread. The controller allows to be configured with 
various
+     * settings to attempt to restart failing routes.
+     */
+    public void setRouteControllerEnabled(boolean routeControllerEnabled) {
+        this.routeControllerEnabled = routeControllerEnabled;
+    }
+
+    public long getRouteControllerInitialDelay() {
+        return routeControllerInitialDelay;
+    }
+
+    /**
+     * Initial delay in milli seconds before the route controller starts, after
+     * CamelContext has been started.
+     */
+    public void setRouteControllerInitialDelay(long 
routeControllerInitialDelay) {
+        this.routeControllerInitialDelay = routeControllerInitialDelay;
+    }
+
+    public long getRouteControllerBackOffDelay() {
+        return routeControllerBackOffDelay;
+    }
+
+    /**
+     * Backoff delay in millis when restarting a route that failed to startup.
+     */
+    public void setRouteControllerBackOffDelay(long 
routeControllerBackOffDelay) {
+        this.routeControllerBackOffDelay = routeControllerBackOffDelay;
+    }
+
+    public long getRouteControllerBackOffMaxDelay() {
+        return routeControllerBackOffMaxDelay;
+    }
+
+    /**
+     * Backoff maximum delay in millis when restarting a route that failed to 
startup.
+     */
+    public void setRouteControllerBackOffMaxDelay(long 
routeControllerBackOffMaxDelay) {
+        this.routeControllerBackOffMaxDelay = routeControllerBackOffMaxDelay;
+    }
+
+    public long getRouteControllerBackOffMaxElapsedTime() {
+        return routeControllerBackOffMaxElapsedTime;
+    }
+
+    /**
+     * Backoff maximum elapsed time in millis, after which the backoff should 
be considered
+     * exhausted and no more attempts should be made.
+     */
+    public void setRouteControllerBackOffMaxElapsedTime(long 
routeControllerBackOffMaxElapsedTime) {
+        this.routeControllerBackOffMaxElapsedTime = 
routeControllerBackOffMaxElapsedTime;
+    }
+
+    public long getRouteControllerBackOffMaxAttempts() {
+        return routeControllerBackOffMaxAttempts;
+    }
+
+    /**
+     * Backoff maximum number of attempts to restart a route that failed to 
startup.
+     * When this threshold has been exceeded then the controller will give up
+     * attempting to restart the route, and the route will remain as stopped.
+     */
+    public void setRouteControllerBackOffMaxAttempts(long 
routeControllerBackOffMaxAttempts) {
+        this.routeControllerBackOffMaxAttempts = 
routeControllerBackOffMaxAttempts;
+    }
+
+    public double getRouteControllerBackOffMultiplier() {
+        return routeControllerBackOffMultiplier;
+    }
+
+    /**
+     * Backoff multiplier to use for exponential backoff. This is used to 
extend the delay
+     * between restart attempts.
+     */
+    public void setRouteControllerBackOffMultiplier(double 
routeControllerBackOffMultiplier) {
+        this.routeControllerBackOffMultiplier = 
routeControllerBackOffMultiplier;
+    }
+
     // fluent builders
     // --------------------------------------------------------------
 
@@ -1537,4 +1633,73 @@ public abstract class DefaultConfigurationProperties<T> {
         this.lightweight = lightweight;
         return (T) this;
     }
+
+    /**
+     * To enable using supervising route controller which allows Camel to 
startup
+     * and then the controller takes care of starting the routes in a safe 
manner.
+     *
+     * This can be used when you want to startup Camel despite a route may 
otherwise
+     * fail fast during startup and cause Camel to fail to startup as well. By 
delegating
+     * the route startup to the supervising route controller then its manages 
the startup
+     * using a background thread. The controller allows to be configured with 
various
+     * settings to attempt to restart failing routes.
+     */
+    public T withRouteControllerEnabled(boolean routeControllerEnabled) {
+        this.routeControllerEnabled = routeControllerEnabled;
+        return (T) this;
+    }
+
+    /**
+     * Initial delay in milli seconds before the route controller starts, after
+     * CamelContext has been started.
+     */
+    public T withRouteControllerInitialDelay(long routeControllerInitialDelay) 
{
+        this.routeControllerInitialDelay = routeControllerInitialDelay;
+        return (T) this;
+    }
+
+    /**
+     * Backoff delay in millis when restarting a route that failed to startup.
+     */
+    public T withRouteControllerBackOffDelay(long routeControllerBackOffDelay) 
{
+        this.routeControllerBackOffDelay = routeControllerBackOffDelay;
+        return (T) this;
+    }
+
+    /**
+     * Backoff maximum delay in millis when restarting a route that failed to 
startup.
+     */
+    public T withRouteControllerBackOffMaxDelay(long 
routeControllerBackOffMaxDelay) {
+        this.routeControllerBackOffMaxDelay = routeControllerBackOffMaxDelay;
+        return (T) this;
+    }
+
+    /**
+     * Backoff maximum elapsed time in millis, after which the backoff should 
be considered
+     * exhausted and no more attempts should be made.
+     */
+    public T withRouteControllerBackOffMaxElapsedTime(long 
routeControllerBackOffMaxElapsedTime) {
+        this.routeControllerBackOffMaxElapsedTime = 
routeControllerBackOffMaxElapsedTime;
+        return (T) this;
+    }
+
+    /**
+     * Backoff maximum number of attempts to restart a route that failed to 
startup.
+     * When this threshold has been exceeded then the controller will give up
+     * attempting to restart the route, and the route will remain as stopped.
+     */
+    public T withRouteControllerBackOffMaxAttempts(long 
routeControllerBackOffMaxAttempts) {
+        this.routeControllerBackOffMaxAttempts = 
routeControllerBackOffMaxAttempts;
+        return (T) this;
+    }
+
+    /**
+     * Backoff multiplier to use for exponential backoff. This is used to 
extend the delay
+     * between restart attempts.
+     */
+    public T withRouteControllerBackOffMultiplier(double 
routeControllerBackOffMultiplier) {
+        this.routeControllerBackOffMultiplier = 
routeControllerBackOffMultiplier;
+        return (T) this;
+    }
+
 }
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainSupervisingRouteControllerTest.java
 
b/core/camel-main/src/test/java/org/apache/camel/main/MainSupervisingRouteControllerTest.java
new file mode 100644
index 0000000..d91699d
--- /dev/null
+++ 
b/core/camel-main/src/test/java/org/apache/camel/main/MainSupervisingRouteControllerTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.main;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Consumer;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.component.seda.SedaComponent;
+import org.apache.camel.component.seda.SedaConsumer;
+import org.apache.camel.component.seda.SedaEndpoint;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MainSupervisingRouteControllerTest extends Assert {
+
+    @Test
+    public void testMain() throws Exception {
+        // lets make a simple route
+        Main main = new Main();
+        main.configure().addRoutesBuilder(new MyRoute());
+        main.configure().setRouteControllerEnabled(true);
+        main.configure().setRouteControllerBackOffDelay(250);
+        main.configure().setRouteControllerBackOffMaxAttempts(3);
+        main.configure().setRouteControllerInitialDelay(1000);
+
+        main.start();
+
+        MockEndpoint mock = main.getCamelContext().getEndpoint("mock:foo", 
MockEndpoint.class);
+        mock.expectedMinimumMessageCount(3);
+
+        MockEndpoint mock2 = main.getCamelContext().getEndpoint("mock:cheese", 
MockEndpoint.class);
+        mock2.expectedMessageCount(0);
+
+        MockEndpoint.assertIsSatisfied(10, TimeUnit.SECONDS, mock, mock2);
+
+        assertEquals("Started", 
main.camelContext.getRouteController().getRouteStatus("foo").toString());
+        // cheese was not able to start
+        assertEquals("Stopped", 
main.camelContext.getRouteController().getRouteStatus("cheese").toString());
+
+        main.stop();
+    }
+
+    private class MyRoute extends RouteBuilder {
+        @Override
+        public void configure() throws Exception {
+            getContext().addComponent("jms", new MyJmsComponent());
+
+            from("timer:foo").to("mock:foo").routeId("foo");
+
+            from("jms:cheese").to("mock:cheese").routeId("cheese");
+        }
+    }
+
+    private class MyJmsComponent extends SedaComponent {
+
+        @Override
+        protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) throws Exception {
+            return new MyJmsEndpoint();
+        }
+    }
+
+    private class MyJmsEndpoint extends SedaEndpoint {
+
+        public MyJmsEndpoint() {
+            super();
+        }
+
+        @Override
+        public Consumer createConsumer(Processor processor) throws Exception {
+            return new MyJmsConsumer(this, processor);
+        }
+
+        @Override
+        protected String createEndpointUri() {
+            return "jms:cheese";
+        }
+    }
+
+    private class MyJmsConsumer extends SedaConsumer {
+
+        public MyJmsConsumer(SedaEndpoint endpoint, Processor processor) {
+            super(endpoint, processor);
+        }
+
+        @Override
+        protected void doStart() throws Exception {
+            throw new IllegalArgumentException("Cannot start");
+        }
+    }
+
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java 
b/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java
index d96fca8..7b6d240 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java
@@ -17,6 +17,7 @@
 package org.apache.camel.util.backoff;
 
 import java.time.Duration;
+import java.time.temporal.TemporalUnit;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.util.ObjectHelper;
@@ -52,9 +53,6 @@ public final class BackOff {
     // Properties
     // *************************************
 
-    /**
-     * @return the delay to wait before retry the operation.
-     */
     public Duration getDelay() {
         return delay;
     }
@@ -96,8 +94,6 @@ public final class BackOff {
     /**
      * The maximum number of attempts after which the back-off should be 
considered
      * exhausted and no more attempts should be made.
-     *
-     * @param maxAttempts
      */
     public void setMaxAttempts(Long maxAttempts) {
         this.maxAttempts = maxAttempts;
@@ -116,13 +112,13 @@ public final class BackOff {
 
     @Override
     public String toString() {
-        return "BackOff{"
+        return "BackOff["
             + "delay=" + delay
-            + ", maxDelay=" + maxDelay
-            + ", maxElapsedTime=" + maxElapsedTime
+            + ", maxDelay=" + (maxDelay != MAX_DURATION ? maxDelay : "")
+            + ", maxElapsedTime=" + (maxElapsedTime != MAX_DURATION ? 
maxElapsedTime : "")
             + ", maxAttempts=" + maxAttempts
             + ", multiplier=" + multiplier
-            + '}';
+            + ']';
     }
 
     // *****************************************

Reply via email to