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 be43a877ce65 CAMEL-26750: camel-core-model - the steps of a circuit 
breaker fallback set by the XML or YAML loader belong to its route (#29430)
be43a877ce65 is described below

commit be43a877ce6556078cbe04f58f76664fcde7b347
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 09:30:59 2026 +0100

    CAMEL-26750: camel-core-model - the steps of a circuit breaker fallback set 
by the XML or YAML loader belong to its route (#29430)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ResilienceFallbackRouteIdTest.java             | 84 ++++++++++++++++++++++
 .../camel/model/CircuitBreakerDefinition.java      | 18 +++++
 2 files changed, 102 insertions(+)

diff --git 
a/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceFallbackRouteIdTest.java
 
b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceFallbackRouteIdTest.java
new file mode 100644
index 000000000000..97dcfe6e3396
--- /dev/null
+++ 
b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceFallbackRouteIdTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.resilience4j;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.MessageHistory;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.model.ChoiceDefinition;
+import org.apache.camel.model.CircuitBreakerDefinition;
+import org.apache.camel.model.OnFallbackDefinition;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The steps of a fallback set as a whole, as the YAML and XML loaders do, 
belong to the route of the circuit breaker:
+ * the message history and the error registry have its route, not the id of 
the first step of the fallback.
+ */
+public class ResilienceFallbackRouteIdTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.setMessageHistory(true);
+        return context;
+    }
+
+    @Test
+    public void testTheStepsOfTheFallbackBelongToTheRoute() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:start", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+        @SuppressWarnings("unchecked")
+        List<MessageHistory> history = 
mock.getReceivedExchanges().get(0).getProperty(Exchange.MESSAGE_HISTORY, 
List.class);
+        List<String> nodes = history.stream().map(h -> 
h.getNode().getId()).toList();
+        assertTrue(nodes.containsAll(List.of("cb", "choice", "fallbackBody")), 
"the steps of the fallback: " + nodes);
+        for (MessageHistory h : history) {
+            assertEquals("start", h.getRouteId(), "the route of " + 
h.getNode().getId());
+        }
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                CircuitBreakerDefinition cb = 
from("direct:start").routeId("start").circuitBreaker();
+                cb.id("cb").throwException(new 
IllegalStateException("Forced"));
+                // the fallback as the loaders set it: built on its own, its 
steps added before it has a parent
+                OnFallbackDefinition fallback = new OnFallbackDefinition();
+                ChoiceDefinition choice = new ChoiceDefinition();
+                choice.id("choice");
+                choice.when(constant(true)).setBody(constant("Fallback 
response")).id("fallbackBody");
+                fallback.addOutput(choice);
+                cb.setOnFallback(fallback);
+                cb.end().to("mock:result");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/CircuitBreakerDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/CircuitBreakerDefinition.java
index d6ae09780133..22f3c53033e5 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/CircuitBreakerDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/CircuitBreakerDefinition.java
@@ -81,6 +81,8 @@ public class CircuitBreakerDefinition extends 
OutputDefinition<CircuitBreakerDef
         this.faultToleranceConfiguration
                 = source.faultToleranceConfiguration != null ? 
source.faultToleranceConfiguration.copyDefinition() : null;
         this.onFallback = source.onFallback != null ? 
source.onFallback.copyDefinition() : null;
+        // the copy of the fallback belongs to this copy, not to the circuit 
breaker it was copied from
+        adoptFallback();
     }
 
     @Override
@@ -172,6 +174,22 @@ public class CircuitBreakerDefinition extends 
OutputDefinition<CircuitBreakerDef
     public void setOnFallback(OnFallbackDefinition onFallback) {
         this.onFallback = onFallback;
         this.fallbackViaFluentApi = false;
+        adoptFallback();
+    }
+
+    /**
+     * The fallback and its steps belong to this circuit breaker, as {@link 
#onFallback()} gives them: a loader (XML,
+     * YAML) builds the fallback with its steps before setting it, so their 
parent is not set, and the steps inside them
+     * (the steps of a choice) would have the first step of the fallback as 
their route, in the message history and the
+     * error registry.
+     */
+    private void adoptFallback() {
+        if (onFallback != null) {
+            onFallback.setParent(this);
+            for (ProcessorDefinition<?> output : onFallback.getOutputs()) {
+                output.setParent(this);
+            }
+        }
     }
 
     // Fluent API

Reply via email to