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 c6ecfea3aa11 CAMEL-25858: camel-main - dev mode: a reload sets all the 
options of a component, and keeps those that did not change (#28140)
c6ecfea3aa11 is described below

commit c6ecfea3aa11eff750b1d0789dd444f830d5c89e
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Oct 20 12:36:03 2026 +0200

    CAMEL-25858: camel-main - dev mode: a reload sets all the options of a 
component, and keeps those that did not change (#28140)
    
    A reload that changed options of a component created the component anew for 
each key, so only the last option was
    set, and the new component lost the options that did not change, as the 
reload passes only the properties that
    changed. The component of a reload is now created once, and configured with 
all its current options (from the
    properties component, ENV variables and JVM system properties, as at 
startup), the options of the reload winning.
    
    
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../org/apache/camel/main/BaseMainSupport.java     | 74 +++++++++++++----
 .../main/MainPropertiesReloadComponentTest.java    | 93 ++++++++++++++++++++++
 2 files changed, 150 insertions(+), 17 deletions(-)

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 87f87a8d12cb..09f1f0063a47 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
@@ -30,6 +30,7 @@ import java.security.KeyStore;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -3052,6 +3053,30 @@ public abstract class BaseMainSupport extends 
BaseService {
     protected void autoConfigurationFromProperties(
             CamelContext camelContext, OrderedLocationProperties 
autoConfiguredProperties)
             throws Exception {
+        OrderedLocationProperties prop = 
loadAutoConfigurationProperties(camelContext);
+
+        Map<PropertyOptionKey, OrderedLocationProperties> properties = new 
LinkedHashMap<>();
+
+        // filter out wildcard properties
+        for (String key : prop.stringPropertyNames()) {
+            if (key.contains("*")) {
+                String loc = prop.getLocation(key);
+                wildcardProperties.put(loc, key, prop.getProperty(key));
+            }
+        }
+        // and remove wildcards
+        for (String key : wildcardProperties.stringPropertyNames()) {
+            prop.remove(key);
+        }
+
+        doAutoConfigurationFromProperties(camelContext, prop, properties, 
false, autoConfiguredProperties);
+    }
+
+    /**
+     * The camel.* properties that configure components, data formats and 
languages: from the properties component,
+     * overridden by ENV variables and then by JVM system properties.
+     */
+    private OrderedLocationProperties 
loadAutoConfigurationProperties(CamelContext camelContext) {
         OrderedLocationProperties prop = new OrderedLocationProperties();
 
         // load properties from properties component (override existing)
@@ -3091,22 +3116,7 @@ public abstract class BaseMainSupport extends 
BaseService {
                 putAllOverride(prop, "SYS", propJVM);
             }
         }
-
-        Map<PropertyOptionKey, OrderedLocationProperties> properties = new 
LinkedHashMap<>();
-
-        // filter out wildcard properties
-        for (String key : prop.stringPropertyNames()) {
-            if (key.contains("*")) {
-                String loc = prop.getLocation(key);
-                wildcardProperties.put(loc, key, prop.getProperty(key));
-            }
-        }
-        // and remove wildcards
-        for (String key : wildcardProperties.stringPropertyNames()) {
-            prop.remove(key);
-        }
-
-        doAutoConfigurationFromProperties(camelContext, prop, properties, 
false, autoConfiguredProperties);
+        return prop;
     }
 
     protected void autoConfigurationFromReloadedProperties(
@@ -3145,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);
+        // 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);
 
         // log summary of configurations
@@ -3153,19 +3165,47 @@ public abstract class BaseMainSupport extends 
BaseService {
         }
     }
 
+    /**
+     * 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
+     * reload win.
+     */
+    private void addOtherOptionsOfReloadedComponents(
+            CamelContext camelContext, OrderedLocationProperties 
reloadedProperties) {
+        Set<String> prefixes = new HashSet<>();
+        for (String key : reloadedProperties.stringPropertyNames()) {
+            int dot = key.startsWith("camel.component.") ? key.indexOf('.', 
"camel.component.".length()) : -1;
+            if (dot > 0) {
+                prefixes.add(key.substring(0, dot + 1));
+            }
+        }
+        if (prefixes.isEmpty()) {
+            return;
+        }
+        OrderedLocationProperties current = 
loadAutoConfigurationProperties(camelContext);
+        for (String key : current.stringPropertyNames()) {
+            if (!key.contains("*") && !reloadedProperties.containsKey(key)
+                    && prefixes.stream().anyMatch(key::startsWith)) {
+                reloadedProperties.put(current.getLocation(key), key, 
current.getProperty(key));
+            }
+        }
+    }
+
     protected void doAutoConfigurationFromProperties(
             CamelContext camelContext, OrderedLocationProperties prop,
             Map<PropertyOptionKey, OrderedLocationProperties> properties, 
boolean reload,
             OrderedLocationProperties autoConfiguredProperties)
             throws Exception {
 
+        // a component is re-created once per reload, so all the options of 
the reload are set on the new one
+        Set<String> recreated = new HashSet<>();
         for (String key : prop.stringPropertyNames()) {
             computeProperties("camel.component.", key, prop, properties, name 
-> {
                 boolean optional = name.startsWith("?");
                 if (optional) {
                     name = name.substring(1);
                 }
-                if (reload) {
+                if (reload && recreated.add(name)) {
                     // force re-creating component on reload
                     camelContext.removeComponent(name);
                 }
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadComponentTest.java
 
b/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadComponentTest.java
new file mode 100644
index 000000000000..b5196de410cc
--- /dev/null
+++ 
b/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadComponentTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.component.log.LogComponent;
+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 configures its components again: 
a component is created anew, once per
+ * reload, and gets every option of the reload, not only the last one.
+ */
+public class MainPropertiesReloadComponentTest {
+
+    @Test
+    public void allTheOptionsOfAComponentInOneReloadAreSet() throws Exception {
+        Main main = main(new Properties());
+        try {
+            main.start();
+
+            reload(main).onChanged("file:application.properties", properties(
+                    "camel.component.log.source-location-logger-name", "true",
+                    "camel.component.log.lazy-start-producer", "true"));
+
+            LogComponent log = main.getCamelContext().getComponent("log", 
LogComponent.class);
+            assertThat(log.isSourceLocationLoggerName()).isTrue();
+            assertThat(log.isLazyStartProducer()).isTrue();
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void anOptionThatDidNotChangeIsKept() throws Exception {
+        Properties startup = new Properties();
+        startup.setProperty("camel.component.log.source-location-logger-name", 
"true");
+        startup.setProperty("camel.component.log.lazy-start-producer", "true");
+        Main main = main(startup);
+        try {
+            main.start();
+            assertThat(main.getCamelContext().getComponent("log", 
LogComponent.class).isSourceLocationLoggerName()).isTrue();
+
+            // the file changed one option of the component
+            reload(main).onChanged("file:application.properties", properties(
+                    "camel.component.log.lazy-start-producer", "false"));
+
+            LogComponent log = main.getCamelContext().getComponent("log", 
LogComponent.class);
+            assertThat(log.isLazyStartProducer()).isFalse();
+            assertThat(log.isSourceLocationLoggerName()).isTrue();
+        } 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