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 624eddac5efa CAMEL-26320: camel-main - a properties file that changed 
in dev mode applies its camel.resilience4j and camel.faulttolerance keys 
(#28509)
624eddac5efa is described below

commit 624eddac5efa2d927dda4a5a8b9da7b01ae3417d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 26 17:22:41 2026 +0100

    CAMEL-26320: camel-main - a properties file that changed in dev mode 
applies its camel.resilience4j and camel.faulttolerance keys (#28509)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../org/apache/camel/main/BaseMainSupport.java     |  38 ++++++++
 .../MainPropertiesReloadCircuitBreakerTest.java    | 100 +++++++++++++++++++++
 2 files changed, 138 insertions(+)

diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java 
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 7fc74449180a..5f7f6b771fa4 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -3155,6 +3155,8 @@ public abstract class BaseMainSupport extends BaseService 
{
         OrderedLocationProperties autoConfiguredProperties = new 
OrderedLocationProperties();
         // the beans first, as the components that are configured next may 
refer to them (CAMEL-25767)
         bindReloadedBeans(camelContext, reloadedProperties, changed, 
autoConfiguredProperties);
+        // the defaults of the circuit breakers, which the routes created 
again after the reload pick up
+        reloadCircuitBreakerDefaults(camelContext, reloadedProperties, 
autoConfiguredProperties);
         // a component of the reload is created anew, so it gets all its 
options, not only those that changed
         addOtherOptionsOfReloadedComponents(camelContext, reloadedProperties);
         doAutoConfigurationFromProperties(camelContext, reloadedProperties, 
properties, true, autoConfiguredProperties);
@@ -3165,6 +3167,42 @@ public abstract class BaseMainSupport extends 
BaseService {
         }
     }
 
+    /**
+     * The camel.resilience4j.* and camel.faulttolerance.* keys of re-loaded 
properties, applied to the default
+     * configurations of the circuit breakers as when the application starts. 
A properties file that changed in dev mode
+     * creates the routes again after this, and a circuit breaker reads the 
defaults when its route is created, so
+     * settings such as slidingWindowSize and failureRateThreshold can be 
tried out without a restart.
+     */
+    private void reloadCircuitBreakerDefaults(
+            CamelContext camelContext, OrderedLocationProperties 
reloadedProperties,
+            OrderedLocationProperties autoConfiguredProperties)
+            throws Exception {
+        OrderedLocationProperties resilience4jProperties = new 
OrderedLocationProperties();
+        OrderedLocationProperties faultToleranceProperties = new 
OrderedLocationProperties();
+        // stringPropertyNames is a live view of the keys, which are removed 
below
+        for (String key : new 
ArrayList<>(reloadedProperties.stringPropertyNames())) {
+            String loc = reloadedProperties.getLocation(key);
+            String value = reloadedProperties.getProperty(key);
+            if (startsWithIgnoreCase(key, "camel.resilience4j.")) {
+                String option = key.substring(19);
+                validateOptionAndValue(key, option, value);
+                resilience4jProperties.put(loc, optionKey(option), value);
+                reloadedProperties.remove(key);
+            } else if (startsWithIgnoreCase(key, "camel.faulttolerance.")) {
+                String option = key.substring(21);
+                validateOptionAndValue(key, option, value);
+                faultToleranceProperties.put(loc, optionKey(option), value);
+                reloadedProperties.remove(key);
+            }
+        }
+        if (!resilience4jProperties.isEmpty() || 
!faultToleranceProperties.isEmpty()) {
+            LOG.debug("Auto-configuring the circuit breakers from reloaded 
properties: {}",
+                    resilience4jProperties.size() + 
faultToleranceProperties.size());
+            
MainSupportModelConfigurer.configureModelCamelContext(camelContext, 
mainConfigurationProperties,
+                    autoConfiguredProperties, resilience4jProperties, 
faultToleranceProperties);
+        }
+    }
+
     /**
      * Adds the other current options of the components that the reloaded 
properties configure, as a reload creates
      * these components anew: without them, a component would lose the options 
that did not change. The options of the
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadCircuitBreakerTest.java
 
b/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadCircuitBreakerTest.java
new file mode 100644
index 000000000000..c70e136bbc6d
--- /dev/null
+++ 
b/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadCircuitBreakerTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.Properties;
+
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.model.Resilience4jConfigurationDefinition;
+import org.apache.camel.util.OrderedLocationProperties;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A properties file that changed in dev mode applies its camel.resilience4j.* 
and camel.faulttolerance.* keys to the
+ * default configurations of the circuit breakers, as when the application 
starts. The routes are created again after
+ * the reload, and a circuit breaker reads the defaults when its route is 
created, so the new values take effect without
+ * a restart.
+ */
+public class MainPropertiesReloadCircuitBreakerTest {
+
+    @Test
+    public void theCircuitBreakerKeysOfAChangedPropertiesFileAreApplied() 
throws Exception {
+        Properties startup = 
properties("camel.resilience4j.sliding-window-size", "100",
+                "camel.resilience4j.failure-rate-threshold", "50");
+        Main main = main(startup);
+        try {
+            main.start();
+            ModelCamelContext model = (ModelCamelContext) 
main.getCamelContext();
+            
assertThat(model.getResilience4jConfiguration(null).getSlidingWindowSize()).isEqualTo("100");
+
+            // application.properties saved in dev mode, with the keys written 
in either form
+            reload(main).onChanged("file:application.properties", properties(
+                    "camel.resilience4j.slidingWindowSize", "4",
+                    "camel.resilience4j.minimum-number-of-calls", "4",
+                    "camel.faulttolerance.requestVolumeThreshold", "6"));
+
+            Resilience4jConfigurationDefinition resilience4j = 
model.getResilience4jConfiguration(null);
+            assertThat(resilience4j.getSlidingWindowSize()).isEqualTo("4");
+            assertThat(resilience4j.getMinimumNumberOfCalls()).isEqualTo("4");
+            // a key that did not change keeps its value (a float in the main 
configuration, so "50.0")
+            
assertThat(Float.parseFloat(resilience4j.getFailureRateThreshold())).isEqualTo(50f);
+            // a configuration that the application did not have before is 
created
+            
assertThat(model.getFaultToleranceConfiguration(null).getRequestVolumeThreshold()).isEqualTo("6");
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void aReloadWithoutCircuitBreakerKeysLeavesTheDefaultsAsTheyAre() 
throws Exception {
+        Main main = main(properties("camel.resilience4j.sliding-window-size", 
"100"));
+        try {
+            main.start();
+            ModelCamelContext model = (ModelCamelContext) 
main.getCamelContext();
+
+            reload(main).onChanged("file:application.properties", 
properties("greeting", "hello"));
+
+            
assertThat(model.getResilience4jConfiguration(null).getSlidingWindowSize()).isEqualTo("100");
+            assertThat(model.getFaultToleranceConfiguration(null)).isNull();
+        } finally {
+            main.stop();
+        }
+    }
+
+    private static Main main(Properties properties) {
+        Main main = new Main();
+        main.setDefaultPropertyPlaceholderLocation("false");
+        main.setOverrideProperties(properties);
+        return main;
+    }
+
+    private static MainPropertiesReload reload(Main main) {
+        MainPropertiesReload reload = new MainPropertiesReload(main);
+        reload.setCamelContext(main.getCamelContext());
+        return reload;
+    }
+
+    private static OrderedLocationProperties properties(String... 
keysAndValues) {
+        OrderedLocationProperties answer = new OrderedLocationProperties();
+        for (int i = 0; i < keysAndValues.length; i += 2) {
+            answer.put("file:application.properties", keysAndValues[i], 
keysAndValues[i + 1]);
+        }
+        return answer;
+    }
+}

Reply via email to