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