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

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


The following commit(s) were added to refs/heads/main by this push:
     new c29cfbb  CAMEL-17726: camel-controlbus - Add fail action to stop and 
mark a route as failed. Add stop route with caused exception to mark the route 
as failed (DOWN). (#7080)
c29cfbb is described below

commit c29cfbbd3a3c2997aec4f601f14274351046de54
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Wed Mar 2 11:13:53 2022 +0100

    CAMEL-17726: camel-controlbus - Add fail action to stop and mark a route as 
failed. Add stop route with caused exception to mark the route as failed 
(DOWN). (#7080)
---
 .../camel/component/controlbus/controlbus.json     |   2 +-
 .../component/controlbus/ControlBusEndpoint.java   |  10 +-
 .../component/controlbus/ControlBusProducer.java   |  13 +++
 .../java/org/apache/camel/spi/RouteController.java |  10 ++
 .../camel/impl/engine/DefaultRouteController.java  |   5 +
 .../engine/DefaultSupervisingRouteController.java  |  13 +++
 .../camel/impl/engine/InternalRouteController.java |  11 +++
 .../impl/lw/LightweightRuntimeCamelContext.java    |   5 +
 .../controlbus/ControlBusFailRouteTest.java        |  65 +++++++++++++
 .../apache/camel/issues/StopAndFailRouteTest.java  |  73 ++++++++++++++
 .../api/management/mbean/ManagedRouteMBean.java    |   3 +
 .../camel/management/mbean/ManagedRoute.java       |  10 ++
 .../management/ManagedRouteStopAndFailTest.java    | 108 +++++++++++++++++++++
 .../modules/ROOT/pages/graceful-shutdown.adoc      |  19 ++++
 14 files changed, 341 insertions(+), 6 deletions(-)

diff --git 
a/components/camel-controlbus/src/generated/resources/org/apache/camel/component/controlbus/controlbus.json
 
b/components/camel-controlbus/src/generated/resources/org/apache/camel/component/controlbus/controlbus.json
index 64d8185..75b407b 100644
--- 
a/components/camel-controlbus/src/generated/resources/org/apache/camel/component/controlbus/controlbus.json
+++ 
b/components/camel-controlbus/src/generated/resources/org/apache/camel/component/controlbus/controlbus.json
@@ -28,7 +28,7 @@
   "properties": {
     "command": { "kind": "path", "displayName": "Command", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "enum": [ "route", "language" ], "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Command can be either route or language" },
     "language": { "kind": "path", "displayName": "Language", "group": 
"producer", "label": "", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.Language", "enum": [ "bean", "constant", "el", 
"exchangeProperty", "file", "groovy", "header", "jsonpath", "mvel", "ognl", 
"ref", "simple", "spel", "sql", "terser", "tokenize", "xpath", "xquery", 
"xtokenize" ], "deprecated": false, "autowired": false, "secret": false, 
"description": "Allows you to specify the name of a Langu [...]
-    "action": { "kind": "parameter", "displayName": "Action", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "enum": [ "start", "stop", "suspend", "resume", "restart", 
"status", "stats" ], "deprecated": false, "autowired": false, "secret": false, 
"description": "To denote an action that can be either: start, stop, or status. 
To either start or stop a route, or to get the status of the route as output in 
the message body. You can use [...]
+    "action": { "kind": "parameter", "displayName": "Action", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "enum": [ "start", "stop", "fail", "suspend", "resume", 
"restart", "status", "stats" ], "deprecated": false, "autowired": false, 
"secret": false, "description": "To denote an action that can be either: start, 
stop, or status. To either start or stop a route, or to get the status of the 
route as output in the message body. You [...]
     "async": { "kind": "parameter", "displayName": "Async", "group": 
"producer", "label": "", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to execute the control bus task 
asynchronously. Important: If this option is enabled, then any result from the 
task is not set on the Exchange. This is only possible if executing tasks 
synchronously." },
     "lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start 
Producer", "group": "producer", "label": "producer", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "description": "Whether the producer 
should be started lazy (on the first message). By starting lazy you can use 
this to allow CamelContext and routes to startup in situations where a producer 
may otherwise fail during sta [...]
     "loggingLevel": { "kind": "parameter", "displayName": "Logging Level", 
"group": "producer", "label": "", "required": false, "type": "object", 
"javaType": "org.apache.camel.LoggingLevel", "enum": [ "TRACE", "DEBUG", 
"INFO", "WARN", "ERROR", "OFF" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "INFO", "description": "Logging level used for 
logging when task is done, or if any exceptions occurred during processing the 
task." },
diff --git 
a/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusEndpoint.java
 
b/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusEndpoint.java
index b4c049a..c0584ec 100644
--- 
a/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusEndpoint.java
+++ 
b/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusEndpoint.java
@@ -48,7 +48,7 @@ public class ControlBusEndpoint extends DefaultEndpoint {
     private Language language;
     @UriParam
     private String routeId;
-    @UriParam(enums = "start,stop,suspend,resume,restart,status,stats")
+    @UriParam(enums = "start,stop,fail,suspend,resume,restart,status,stats")
     private String action;
     @UriParam(defaultValue = "1000")
     private int restartDelay = 1000;
@@ -114,10 +114,10 @@ public class ControlBusEndpoint extends DefaultEndpoint {
      * To denote an action that can be either: start, stop, or status.
      * <p/>
      * To either start or stop a route, or to get the status of the route as 
output in the message body. You can use
-     * suspend and resume from Camel 2.11.1 onwards to either suspend or 
resume a route. And from Camel 2.11.1 onwards
-     * you can use stats to get performance statics returned in XML format; 
the routeId option can be used to define
-     * which route to get the performance stats for, if routeId is not 
defined, then you get statistics for the entire
-     * CamelContext. The restart action will restart the route.
+     * suspend and resume to either suspend or resume a route. You can use 
stats to get performance statics returned in
+     * XML format; the routeId option can be used to define which route to get 
the performance stats for, if routeId is
+     * not defined, then you get statistics for the entire CamelContext. The 
restart action will restart the route. And
+     * the fail action will stop and mark the route as failed (stopped due to 
an exception)
      */
     public void setAction(String action) {
         this.action = action;
diff --git 
a/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusProducer.java
 
b/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusProducer.java
index b03c463..9e7ce05 100644
--- 
a/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusProducer.java
+++ 
b/components/camel-controlbus/src/main/java/org/apache/camel/component/controlbus/ControlBusProducer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.component.controlbus;
 
+import java.util.concurrent.RejectedExecutionException;
+
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
 
@@ -163,6 +165,17 @@ public class ControlBusProducer extends 
DefaultAsyncProducer {
                 } else if ("stop".equals(action)) {
                     LOG.debug("Stopping route: {}", id);
                     
getEndpoint().getCamelContext().getRouteController().stopRoute(id);
+                } else if ("fail".equals(action)) {
+                    LOG.debug("Stopping and failing route: {}", id);
+                    // is there any caused exception from the exchange to mark 
the route as failed due
+                    Throwable cause = exchange.getException();
+                    if (cause == null) {
+                        cause = 
exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
+                    }
+                    if (cause == null) {
+                        cause = new RejectedExecutionException("Route " + id + 
" is forced stopped and marked as failed");
+                    }
+                    
getEndpoint().getCamelContext().getRouteController().stopRoute(id, cause);
                 } else if ("suspend".equals(action)) {
                     LOG.debug("Suspending route: {}", id);
                     
getEndpoint().getCamelContext().getRouteController().suspendRoute(id);
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java
index 911f9c2..3f1c4f6 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java
@@ -125,6 +125,16 @@ public interface RouteController extends 
CamelContextAware, StaticService {
     void stopRoute(String routeId) throws Exception;
 
     /**
+     * Stops and marks the given route as failed (health check is DOWN) due to 
a caused exception.
+     *
+     * @param  routeId   the route id
+     * @param  cause     the exception that is causing this route to be 
stopped and marked as failed
+     * @throws Exception is thrown if the route could not be stopped for 
whatever reason
+     * @see              #suspendRoute(String)
+     */
+    void stopRoute(String routeId, Throwable cause) throws Exception;
+
+    /**
      * Stops the given route using {@link 
org.apache.camel.spi.ShutdownStrategy} with a specified timeout.
      *
      * @param  routeId   the route id
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
index e5401f2..0fc55b7 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
@@ -125,6 +125,11 @@ public class DefaultRouteController extends ServiceSupport 
implements RouteContr
     }
 
     @Override
+    public void stopRoute(String routeId, Throwable cause) throws Exception {
+        getInternalRouteController().stopRoute(routeId, cause);
+    }
+
+    @Override
     public void stopRoute(String routeId, long timeout, TimeUnit timeUnit) 
throws Exception {
         getInternalRouteController().stopRoute(routeId, timeout, timeUnit);
     }
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
index a70ace4..545a39c 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
@@ -265,6 +265,19 @@ public class DefaultSupervisingRouteController extends 
DefaultRouteController im
     }
 
     @Override
+    public void stopRoute(String routeId, Throwable cause) throws Exception {
+        final Optional<RouteHolder> route = routes.stream().filter(r -> 
r.getId().equals(routeId)).findFirst();
+
+        if (!route.isPresent()) {
+            // This route is unknown to this controller, apply default 
behaviour
+            // from super class.
+            super.stopRoute(routeId, cause);
+        } else {
+            doStopRoute(route.get(), true, r -> super.stopRoute(routeId, 
cause));
+        }
+    }
+
+    @Override
     public void stopRoute(String routeId, long timeout, TimeUnit timeUnit) 
throws Exception {
         final Optional<RouteHolder> route = routes.stream().filter(r -> 
r.getId().equals(routeId)).findFirst();
 
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
index 95f378e..97130b5 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
@@ -24,6 +24,7 @@ import org.apache.camel.LoggingLevel;
 import org.apache.camel.Route;
 import org.apache.camel.ServiceStatus;
 import org.apache.camel.spi.RouteController;
+import org.apache.camel.spi.RouteError;
 import org.apache.camel.spi.SupervisingRouteController;
 
 /**
@@ -103,6 +104,16 @@ class InternalRouteController implements RouteController {
     }
 
     @Override
+    public void stopRoute(String routeId, Throwable cause) throws Exception {
+        Route route = abstractCamelContext.getRoute(routeId);
+        if (route != null) {
+            abstractCamelContext.stopRoute(routeId);
+            // and mark the route as failed and unhealthy (DOWN)
+            route.setLastError(new DefaultRouteError(RouteError.Phase.STOP, 
cause, true));
+        }
+    }
+
+    @Override
     public void stopRoute(String routeId, long timeout, TimeUnit timeUnit) 
throws Exception {
         abstractCamelContext.stopRoute(routeId, timeout, timeUnit);
     }
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
index 1bc5b25..034a1ee 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
@@ -2163,6 +2163,11 @@ public class LightweightRuntimeCamelContext implements 
ExtendedCamelContext, Cat
             }
 
             @Override
+            public void stopRoute(String routeId, Throwable cause) throws 
Exception {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
             public void stopRoute(String routeId, long timeout, TimeUnit 
timeUnit) throws Exception {
                 throw new UnsupportedOperationException();
             }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/controlbus/ControlBusFailRouteTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/controlbus/ControlBusFailRouteTest.java
new file mode 100644
index 0000000..6a09033
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/controlbus/ControlBusFailRouteTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.component.controlbus;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Route;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.RouteError;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ControlBusFailRouteTest extends ContextTestSupport {
+
+    @Test
+    public void testControlBusFail() throws Exception {
+        assertEquals("Started", 
context.getRouteController().getRouteStatus("foo").name());
+
+        template.sendBody("direct:foo", "Hello World");
+
+        // runs async so it can take a little while
+        await().atMost(5, TimeUnit.SECONDS).until(() -> 
context.getRouteController().getRouteStatus("foo").isStopped());
+
+        Route route = context.getRoute("foo");
+        RouteError re = route.getLastError();
+        Assertions.assertNotNull(re);
+        Assertions.assertTrue(re.isUnhealthy());
+        Assertions.assertEquals(RouteError.Phase.STOP, re.getPhase());
+        Throwable cause = re.getException();
+        Assertions.assertNotNull(cause);
+        Assertions.assertEquals("Forced by Donkey Kong", cause.getMessage());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
errorHandler(deadLetterChannel("controlbus:route?routeId=current&action=fail&async=true"));
+
+                from("direct:foo").routeId("foo")
+                        .throwException(new IllegalArgumentException("Forced 
by Donkey Kong"));
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/issues/StopAndFailRouteTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/issues/StopAndFailRouteTest.java
new file mode 100644
index 0000000..9fb0e26
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/issues/StopAndFailRouteTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.issues;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Route;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.RouteError;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class StopAndFailRouteTest extends ContextTestSupport {
+
+    @Test
+    public void stopRoute() throws Exception {
+        Route route = context.getRoute("foo");
+        Assertions.assertNull(route.getLastError());
+
+        context.getRouteController().stopRoute("foo");
+
+        assertEquals("Stopped", 
context.getRouteController().getRouteStatus("foo").name());
+
+        RouteError re = route.getLastError();
+        Assertions.assertNull(re);
+    }
+
+    @Test
+    public void failRoute() throws Exception {
+        Route route = context.getRoute("bar");
+        Assertions.assertNull(route.getLastError());
+
+        Throwable cause = new IllegalArgumentException("Forced");
+        context.getRouteController().stopRoute("bar", cause);
+
+        assertEquals("Stopped", 
context.getRouteController().getRouteStatus("bar").name());
+
+        RouteError re = route.getLastError();
+        Assertions.assertNotNull(re);
+        Assertions.assertTrue(re.isUnhealthy());
+        Assertions.assertEquals(RouteError.Phase.STOP, re.getPhase());
+        Assertions.assertSame(cause, re.getException());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:foo").routeId("foo")
+                        .to("mock:foo");
+
+                from("direct:bar").routeId("bar")
+                        .to("mock:bar");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
index dfd3667..5cf705e 100644
--- 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
+++ 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
@@ -89,6 +89,9 @@ public interface ManagedRouteMBean extends 
ManagedPerformanceCounterMBean {
     @ManagedOperation(description = "Stop route")
     void stop() throws Exception;
 
+    @ManagedOperation(description = "Stop and marks the route as failed 
(health-check reporting as DOWN)")
+    void stopAndFail() throws Exception;
+
     @ManagedOperation(description = "Stop route (using timeout in seconds)")
     void stop(long timeout) throws Exception;
 
diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
index 8b38164..436bc8f 100644
--- 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
 
 import javax.management.AttributeValueExp;
@@ -290,6 +291,15 @@ public class ManagedRoute extends 
ManagedPerformanceCounter implements TimerList
     }
 
     @Override
+    public void stopAndFail() throws Exception {
+        if (!context.getStatus().isStarted()) {
+            throw new IllegalArgumentException("CamelContext is not started");
+        }
+        Throwable cause = new RejectedExecutionException("Route " + 
getRouteId() + " is forced stopped and marked as failed");
+        context.getRouteController().stopRoute(getRouteId(), cause);
+    }
+
+    @Override
     public void stop(long timeout) throws Exception {
         if (!context.getStatus().isStarted()) {
             throw new IllegalArgumentException("CamelContext is not started");
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteStopAndFailTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteStopAndFailTest.java
new file mode 100644
index 0000000..3e12f90
--- /dev/null
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteStopAndFailTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.management;
+
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.Route;
+import org.apache.camel.ServiceStatus;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.RouteError;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@DisabledOnOs(OS.AIX)
+public class ManagedRouteStopAndFailTest extends ManagementTestSupport {
+
+    @Test
+    public void testStopAndFailRoute() throws Exception {
+        // fire a message to get it running
+        getMockEndpoint("mock:result").expectedMessageCount(1);
+        template.sendBody("direct:start", "Hello World");
+        assertMockEndpointsSatisfied();
+
+        MBeanServer mbeanServer = getMBeanServer();
+
+        Set<ObjectName> set = mbeanServer.queryNames(new 
ObjectName("*:type=routes,*"), null);
+        assertEquals(1, set.size());
+
+        ObjectName on = set.iterator().next();
+
+        boolean registered = mbeanServer.isRegistered(on);
+        assertEquals(true, registered, "Should be registered");
+
+        String uri = (String) mbeanServer.getAttribute(on, "EndpointUri");
+        // the route has this starting endpoint uri
+        assertEquals("direct://start", uri);
+
+        // should be started
+        String state = (String) mbeanServer.getAttribute(on, "State");
+        assertEquals(ServiceStatus.Started.name(), state, "Should be started");
+
+        String uptime = (String) mbeanServer.getAttribute(on, "Uptime");
+        assertNotNull(uptime);
+        log.info("Uptime: {}", uptime);
+
+        long uptimeMillis = (Long) mbeanServer.getAttribute(on, 
"UptimeMillis");
+        assertTrue(uptimeMillis > 0);
+
+        String routeId = (String) mbeanServer.getAttribute(on, "RouteId");
+
+        mbeanServer.invoke(on, "stopAndFail", null, null);
+
+        registered = mbeanServer.isRegistered(on);
+        assertEquals(true, registered, "Should be registered");
+
+        // should be stopped, eg its removed
+        state = (String) mbeanServer.getAttribute(on, "State");
+        assertEquals(ServiceStatus.Stopped.name(), state, "Should be stopped");
+
+        uptime = (String) mbeanServer.getAttribute(on, "Uptime");
+        assertEquals("", uptime);
+
+        uptimeMillis = (Long) mbeanServer.getAttribute(on, "UptimeMillis");
+        assertEquals(0, uptimeMillis);
+
+        Route route = context.getRoute(routeId);
+        RouteError re = route.getLastError();
+        Assertions.assertNotNull(re);
+        Assertions.assertTrue(re.isUnhealthy());
+        Assertions.assertEquals(RouteError.Phase.STOP, re.getPhase());
+        Assertions.assertEquals("Route " + routeId + " is forced stopped and 
marked as failed", re.getException().getMessage());
+
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
from("direct:start").delayer(10).to("log:foo").to("mock:result");
+            }
+        };
+    }
+
+}
diff --git a/docs/user-manual/modules/ROOT/pages/graceful-shutdown.adoc 
b/docs/user-manual/modules/ROOT/pages/graceful-shutdown.adoc
index 0110364..cafa4c7 100644
--- a/docs/user-manual/modules/ROOT/pages/graceful-shutdown.adoc
+++ b/docs/user-manual/modules/ROOT/pages/graceful-shutdown.adoc
@@ -161,6 +161,25 @@ camelContext.getRouteController().stopRoute(routeId);
 
 Routes can also be stopped via JMX.
 
+=== Stopping and marking routes as failed due to an exception
+
+It is possible to stop and fail (will do a gracefully shut down) an individual 
route using
+`stopRoute(routeId, cause)` method as shown:
+
+[source,java]
+----
+Exception cause = ...
+camelContext.getRouteController().stopRoute(routeId, cause);
+----
+
+This will stop the route and then mark the route as failed with the caused 
exception.
+
+NOTE: The Camel xref:health-check.adoc[Health Check] detect the route as 
failed and report it as DOWN.
+If the route is manually stopped, then the route is not marked as failed,
+and the xref:health-check.adoc[Health Check] will report the status as UNKNOWN.
+
+Routes can also be stopped and failed via JMX.
+
 == Implementing custom component or ShutdownStrategy
 
 If you develop your own Camel component or want to implement your own

Reply via email to