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;
+ }
+}