This is an automated email from the ASF dual-hosted git repository.
gnodet 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 470d1086cbd6 CAMEL-25352: fix
CAMEL_COMPONENT_*/CAMEL_DATAFORMAT_*/CAMEL_LANGUAGE_* env vars ignored and
ambiguous name lookup
470d1086cbd6 is described below
commit 470d1086cbd688f4d239e3b197c0fca95f3157f9
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Oct 7 15:16:15 2026 +0200
CAMEL-25352: fix CAMEL_COMPONENT_*/CAMEL_DATAFORMAT_*/CAMEL_LANGUAGE_* env
vars ignored and ambiguous name lookup
- Fix filter prefix mismatch in
BaseMainSupport.autoConfigurationFromProperties: pass uppercase
underscore prefixes (CAMEL_COMPONENT_, CAMEL_DATAFORMAT_,
CAMEL_LANGUAGE_) to filterEnvVariables
which uppercases keys before comparing, so the previous lowercase dotted
prefixes never matched
- Fix non-deterministic name lookup for overlapping catalog names (e.g.
NETTY vs NETTY_HTTP,
SJMS vs SJMS2, JS vs JSONPATH): replace startsWith/findFirst on a HashSet
with a longestMatch
helper that uses exact boundary check (name + _) and picks the longest
match deterministically
- Fix self-defeating workaround examples in upgrade guide (env placeholder
form does not suppress
direct env-var auto-binding)
- Add tests: testLongestMatchComponentEnvVariables,
testLongestMatchLanguageEnvVariables,
testLongestMatchDataFormatEnvVariables, testCustomPassPrefixGuard,
testCamelCaseLanguageEnvVariables, MainEnvVarComponentConfigTest
(end-to-end)
- Simplify loadLines: drop unused valueFunc parameter
- Upgrade guide: document
autoConfigurationEnvironmentVariablesEnabled=false scope, empty
variable failure, rename remedy with placeholder reference, and
custom-named instance limitation
---
.../org/apache/camel/main/BaseMainSupport.java | 2 +-
.../java/org/apache/camel/main/MainHelper.java | 62 ++++++++---
.../camel/main/MainEnvVarComponentConfigTest.java | 103 +++++++++++++++++
.../java/org/apache/camel/main/MainHelperTest.java | 124 ++++++++++++++++++++-
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 52 +++++++++
5 files changed, 319 insertions(+), 24 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 7a094b567152..8799470ba586 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
@@ -3020,7 +3020,7 @@ public abstract class BaseMainSupport extends BaseService
{
// load properties from ENV (override existing)
if
(mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
Map<String, String> env = MainHelper
- .filterEnvVariables(new String[] { "camel.component.",
"camel.dataformat.", "camel.language." });
+ .filterEnvVariables(new String[] { "CAMEL_COMPONENT_",
"CAMEL_DATAFORMAT_", "CAMEL_LANGUAGE_" });
LOG.debug("Gathered {} ENV variables to configure components,
dataformats, languages", env.size());
// special configuration when using ENV variables as we need to
extract the ENV variables
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
b/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
index d22967d9706a..524e8b0281b4 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -59,9 +60,9 @@ public final class MainHelper {
private final String version;
private final StopWatch stopWatch;
- private final Set<String> componentEnvNames = new HashSet<>();
- private final Set<String> dataformatEnvNames = new HashSet<>();
- private final Set<String> languageEnvNames = new HashSet<>();
+ private final Map<String, String> componentEnvNames = new HashMap<>();
+ private final Map<String, String> dataformatEnvNames = new HashMap<>();
+ private final Map<String, String> languageEnvNames = new HashMap<>();
public MainHelper() {
stopWatch = new StopWatch();
@@ -163,17 +164,20 @@ public final class MainHelper {
Set<String> toRemove = new HashSet<>();
env.forEach((k, v) -> {
if (custom) {
+ if (!k.startsWith("CAMEL_COMPONENT_")) {
+ return;
+ }
toRemove.add(k);
String ck = "camel.component." +
k.substring(16).toLowerCase(Locale.US).replace('_', '-');
ck = ck.replaceFirst("-", ".");
properties.put(ck, v);
} else {
- Optional<String> e
- =
componentEnvNames.stream().filter(k::startsWith).findFirst();
+ Optional<Map.Entry<String, String>> e =
longestMatch(componentEnvNames, k);
if (e.isPresent()) {
toRemove.add(k);
- String cname = "camel.component." +
e.get().substring(16).toLowerCase(Locale.US).replace('_', '-');
- String option = k.substring(cname.length() +
1).toLowerCase(Locale.US).replace('_', '-');
+ String catalogName = e.get().getValue();
+ String cname = "camel.component." + catalogName;
+ String option = k.substring(e.get().getKey().length() +
1).toLowerCase(Locale.US).replace('_', '-');
properties.put(cname + "." + option, v);
}
}
@@ -185,17 +189,20 @@ public final class MainHelper {
Set<String> toRemove = new HashSet<>();
env.forEach((k, v) -> {
if (custom) {
+ if (!k.startsWith("CAMEL_DATAFORMAT_")) {
+ return;
+ }
toRemove.add(k);
String ck = "camel.dataformat." +
k.substring(17).toLowerCase(Locale.US).replace('_', '-');
ck = ck.replaceFirst("-", ".");
properties.put(ck, v);
} else {
- Optional<String> e
- =
dataformatEnvNames.stream().filter(k::startsWith).findFirst();
+ Optional<Map.Entry<String, String>> e =
longestMatch(dataformatEnvNames, k);
if (e.isPresent()) {
toRemove.add(k);
- String cname = "camel.dataformat." +
e.get().substring(17).toLowerCase(Locale.US).replace('_', '-');
- String option = k.substring(cname.length() +
1).toLowerCase(Locale.US).replace('_', '-');
+ String catalogName = e.get().getValue();
+ String cname = "camel.dataformat." + catalogName;
+ String option = k.substring(e.get().getKey().length() +
1).toLowerCase(Locale.US).replace('_', '-');
properties.put(cname + "." + option, v);
}
}
@@ -207,17 +214,20 @@ public final class MainHelper {
Set<String> toRemove = new HashSet<>();
env.forEach((k, v) -> {
if (custom) {
+ if (!k.startsWith("CAMEL_LANGUAGE_")) {
+ return;
+ }
toRemove.add(k);
String ck = "camel.language." +
k.substring(15).toLowerCase(Locale.US).replace('_', '-');
ck = ck.replaceFirst("-", ".");
properties.put(ck, v);
} else {
- Optional<String> e
- =
languageEnvNames.stream().filter(k::startsWith).findFirst();
+ Optional<Map.Entry<String, String>> e =
longestMatch(languageEnvNames, k);
if (e.isPresent()) {
toRemove.add(k);
- String cname = "camel.language." +
e.get().substring(15).toLowerCase(Locale.US).replace('_', '-');
- String option = k.substring(cname.length() +
1).toLowerCase(Locale.US).replace('_', '-');
+ String catalogName = e.get().getValue();
+ String cname = "camel.language." + catalogName;
+ String option = k.substring(e.get().getKey().length() +
1).toLowerCase(Locale.US).replace('_', '-');
properties.put(cname + "." + option, v);
}
}
@@ -476,6 +486,21 @@ public final class MainHelper {
}
}
+ /**
+ * Returns the longest name from {@code names} such that {@code envVarKey}
starts with that name followed by
+ * {@code _}, or {@link Optional#empty()} if no such name exists.
+ * <p>
+ * Using the longest match is critical when the catalog contains prefix
pairs such as {@code CAMEL_COMPONENT_NETTY}
+ * / {@code CAMEL_COMPONENT_NETTY_HTTP} or {@code CAMEL_LANGUAGE_JS} /
{@code CAMEL_LANGUAGE_JSONPATH}: a simple
+ * {@code findFirst()} on a {@link java.util.HashSet} gives a
non-deterministic result, whereas the longest match
+ * always selects the most specific name.
+ */
+ private static Optional<Map.Entry<String, String>>
longestMatch(Map<String, String> names, String envVarKey) {
+ return names.entrySet().stream()
+ .filter(e -> envVarKey.startsWith(e.getKey() + "_"))
+ .max(Comparator.comparingInt(e -> e.getKey().length()));
+ }
+
/**
* Loads the entire stream into memory as a String and returns it.
* <p/>
@@ -483,13 +508,16 @@ public final class MainHelper {
* <p/>
* Warning, don't use for crazy big streams :)
*/
- private static void loadLines(InputStream in, Set<String> lines,
Function<String, String> func) throws IOException {
+ private static void loadLines(
+ InputStream in, Map<String, String> map,
+ Function<String, String> keyFunc)
+ throws IOException {
if (in != null) {
try (final InputStreamReader isr = new InputStreamReader(in);
final BufferedReader reader = new LineNumberReader(isr)) {
String line;
while ((line = reader.readLine()) != null) {
- lines.add(func.apply(line));
+ map.put(keyFunc.apply(line), line);
}
}
}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/MainEnvVarComponentConfigTest.java
b/core/camel-main/src/test/java/org/apache/camel/main/MainEnvVarComponentConfigTest.java
new file mode 100644
index 000000000000..ee287abf6fd3
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/MainEnvVarComponentConfigTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.lang.reflect.Field;
+import java.util.Map;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.seda.SedaComponent;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.parallel.Isolated;
+import org.junit.jupiter.api.parallel.ResourceLock;
+import org.junit.jupiter.api.parallel.Resources;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * End-to-end test for {@code CAMEL_COMPONENT_*} environment variable
processing through {@link BaseMainSupport}.
+ * Verifies that the bug-1 fix (using uppercase prefixes in {@code
filterEnvVariables}) actually reaches the component
+ * at runtime.
+ * <p>
+ * Reverting the one-line change in {@code BaseMainSupport} (back to lowercase
{@code "camel.component."}) leaves every
+ * {@link MainHelperTest} green but breaks this test, because only this test
exercises the full
+ * {@code filterEnvVariables → addComponentEnvVariables} path inside {@code
BaseMainSupport}.
+ */
+@Isolated
+@ResourceLock(Resources.SYSTEM_PROPERTIES)
+@DisabledOnOs(OS.WINDOWS)
+public class MainEnvVarComponentConfigTest {
+
+ private String previousValue;
+
+ @Test
+ public void testCamelComponentEnvVarConfiguresComponent() {
+ // Inject CAMEL_COMPONENT_SEDA_QUEUE_SIZE=100 into the process
environment
+ setEnv("CAMEL_COMPONENT_SEDA_QUEUE_SIZE", "100");
+
+ Main main = new Main();
+ main.configure().addRoutesBuilder(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").to("seda:test");
+ }
+ });
+ main.start();
+
+ try {
+ SedaComponent seda = main.getCamelContext().getComponent("seda",
SedaComponent.class);
+ assertNotNull(seda);
+ assertEquals(100, seda.getQueueSize(),
+ "CAMEL_COMPONENT_SEDA_QUEUE_SIZE=100 must configure
seda.queueSize via BaseMainSupport");
+ } finally {
+ main.stop();
+ }
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Restore original env state
+ if (previousValue != null) {
+ getEditableEnv().put("CAMEL_COMPONENT_SEDA_QUEUE_SIZE",
previousValue);
+ } else {
+ getEditableEnv().remove("CAMEL_COMPONENT_SEDA_QUEUE_SIZE");
+ }
+ }
+
+ private void setEnv(String name, String value) {
+ Map<String, String> env = getEditableEnv();
+ previousValue = env.get(name);
+ env.put(name, value);
+ }
+
+ private static Map<String, String> getEditableEnv() {
+ Class<?> classOfMap = System.getenv().getClass();
+ try {
+ Field field = classOfMap.getDeclaredField("m");
+ field.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ Map<String, String> m = (Map<String, String>)
field.get(System.getenv());
+ return m;
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException("Cannot access System.getenv() map", e);
+ }
+ }
+}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
b/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
index d928f15aa5dc..d073f74dfda1 100644
--- a/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.main;
+import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@@ -23,12 +24,12 @@ import org.apache.camel.util.OrderedProperties;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-public class MainHelperTest {
+class MainHelperTest {
private final MainHelper helper = new MainHelper();
@Test
- public void testAddComponentEnvVariables() {
+ void testAddComponentEnvVariables() {
Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_COMPONENT_" });
env.put("CAMEL_COMPONENT_AWS2_S3_ACCESS_KEY", "mysecretkey");
Properties prop = new OrderedProperties();
@@ -40,7 +41,7 @@ public class MainHelperTest {
}
@Test
- public void testAddDataFormatEnvVariables() {
+ void testAddDataFormatEnvVariables() {
Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_DATAFORMAT_" });
env.put("CAMEL_DATAFORMAT_BASE64_LINE_LENGTH", "64");
env.put("CAMEL_DATAFORMAT_JACKSONXML_PRETTYPRINT", "true");
@@ -50,11 +51,11 @@ public class MainHelperTest {
Assertions.assertEquals(0, env.size());
Assertions.assertEquals(2, prop.size());
Assertions.assertEquals("64",
prop.getProperty("camel.dataformat.base64.line-length"));
- Assertions.assertEquals("true",
prop.getProperty("camel.dataformat.jacksonxml.prettyprint"));
+ Assertions.assertEquals("true",
prop.getProperty("camel.dataformat.jacksonXml.prettyprint"));
}
@Test
- public void testAddLanguageEnvVariables() {
+ void testAddLanguageEnvVariables() {
Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_LANGUAGE_" });
env.put("CAMEL_LANGUAGE_JAVA_PRE_COMPILE", "false");
Properties prop = new OrderedProperties();
@@ -66,7 +67,7 @@ public class MainHelperTest {
}
@Test
- public void testAddCustomComponentEnvVariables() {
+ void testAddCustomComponentEnvVariables() {
Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_COMPONENT_" });
env.put("CAMEL_COMPONENT_AWS2_S3_ACCESS_KEY", "mysecretkey");
env.put("CAMEL_COMPONENT_FOO_VERBOSE", "true");
@@ -87,4 +88,115 @@ public class MainHelperTest {
Assertions.assertEquals("false",
prop.getProperty("camel.component.foo.pretty-print"));
}
+ /**
+ * Verifies that overlapping component names (e.g. NETTY vs NETTY_HTTP,
SJMS vs SJMS2, FILE vs FILE_WATCH) are
+ * resolved by longest-match so that each variable is mapped to the
correct component.
+ */
+ @Test
+ void testLongestMatchComponentEnvVariables() {
+ Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_COMPONENT_" });
+ // netty-http must not be mapped to netty
+ env.put("CAMEL_COMPONENT_NETTY_HTTP_MUTE_EXCEPTION", "true");
+ // sjms2 must not be mapped to sjms
+ env.put("CAMEL_COMPONENT_SJMS2_RECOVERY_INTERVAL", "2000");
+ // file-watch must not be mapped to file
+ env.put("CAMEL_COMPONENT_FILE_WATCH_QUEUE_SIZE", "5000");
+ // netty itself (no suffix clash)
+ env.put("CAMEL_COMPONENT_NETTY_RECEIVE_BUFFER_SIZE", "65536");
+
+ Properties prop = new OrderedProperties();
+ helper.addComponentEnvVariables(env, prop, false);
+
+ Assertions.assertEquals(0, env.size());
+ Assertions.assertEquals(4, prop.size());
+ Assertions.assertEquals("true",
prop.getProperty("camel.component.netty-http.mute-exception"));
+ Assertions.assertEquals("2000",
prop.getProperty("camel.component.sjms2.recovery-interval"));
+ Assertions.assertEquals("5000",
prop.getProperty("camel.component.file-watch.queue-size"));
+ Assertions.assertEquals("65536",
prop.getProperty("camel.component.netty.receive-buffer-size"));
+ }
+
+ /**
+ * Verifies that overlapping language names (e.g. JS vs JSONPATH) are
resolved by longest-match.
+ */
+ @Test
+ void testLongestMatchLanguageEnvVariables() {
+ Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_LANGUAGE_" });
+ // jsonpath must not be mapped to js
+ env.put("CAMEL_LANGUAGE_JSONPATH_SUPPRESS_EXCEPTIONS", "true");
+
+ Properties prop = new OrderedProperties();
+ helper.addLanguageEnvVariables(env, prop, false);
+
+ Assertions.assertEquals(0, env.size());
+ Assertions.assertEquals(1, prop.size());
+ Assertions.assertEquals("true",
prop.getProperty("camel.language.jsonpath.suppress-exceptions"));
+ }
+
+ /**
+ * Verifies that overlapping dataformat names (e.g. AVRO vs AVROJACKSON)
are resolved by longest-match.
+ */
+ @Test
+ void testLongestMatchDataFormatEnvVariables() {
+ Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_DATAFORMAT_" });
+ // avroJackson must not be mapped to avro
+ env.put("CAMEL_DATAFORMAT_AVROJACKSON_AUTO_DISCOVER_OBJECT_MAPPER",
"true");
+ // avro itself
+ env.put("CAMEL_DATAFORMAT_AVRO_INSTANCE_CLASS_NAME",
"com.example.MySchema");
+
+ Properties prop = new OrderedProperties();
+ helper.addDataFormatEnvVariables(env, prop, false);
+
+ Assertions.assertEquals(0, env.size());
+ Assertions.assertEquals(2, prop.size());
+ Assertions.assertEquals("true",
prop.getProperty("camel.dataformat.avroJackson.auto-discover-object-mapper"));
+ Assertions.assertEquals("com.example.MySchema",
prop.getProperty("camel.dataformat.avro.instance-class-name"));
+ }
+
+ /**
+ * Verifies that the custom pass only takes its own prefix:
CAMEL_LANGUAGE_* and CAMEL_DATAFORMAT_* leftovers must
+ * not bleed into the component bucket.
+ */
+ @Test
+ void testCustomPassPrefixGuard() {
+ // Simulate: catalog pass consumed aws2-s3 but left unknowns for other
types
+ Map<String, String> env = new HashMap<>();
+ env.put("CAMEL_LANGUAGE_FOO_BAR", "x");
+ env.put("CAMEL_DATAFORMAT_FOO_BAR", "y");
+
+ Properties prop = new OrderedProperties();
+ // component custom pass: must not consume CAMEL_LANGUAGE_* or
CAMEL_DATAFORMAT_*
+ helper.addComponentEnvVariables(env, prop, true);
+ Assertions.assertEquals(2, env.size(),
+ "CAMEL_LANGUAGE_* and CAMEL_DATAFORMAT_* must be left in env
after component custom pass");
+ Assertions.assertEquals(0, prop.size(), "No camel.component.* must be
produced from non-component prefixes");
+
+ // dataformat custom pass: must consume CAMEL_DATAFORMAT_* only
+ helper.addDataFormatEnvVariables(env, prop, true);
+ Assertions.assertEquals(1, env.size(), "CAMEL_LANGUAGE_* must remain
after dataformat custom pass");
+ Assertions.assertEquals("y",
prop.getProperty("camel.dataformat.foo.bar"));
+
+ // language custom pass: must consume CAMEL_LANGUAGE_* only
+ helper.addLanguageEnvVariables(env, prop, true);
+ Assertions.assertEquals(0, env.size());
+ Assertions.assertEquals("x",
prop.getProperty("camel.language.foo.bar"));
+ }
+
+ /**
+ * Verifies that camelCase language names (e.g. exchangeProperty) are
preserved through the catalog map rather than
+ * lowercased.
+ */
+ @Test
+ void testCamelCaseLanguageEnvVariables() {
+ Map<String, String> env = MainHelper.filterEnvVariables(new String[] {
"CAMEL_LANGUAGE_" });
+ // exchangeProperty is a camelCase language name — must not become
"exchangeproperty"
+ env.put("CAMEL_LANGUAGE_EXCHANGEPROPERTY_TRIM", "false");
+
+ Properties prop = new OrderedProperties();
+ helper.addLanguageEnvVariables(env, prop, false);
+
+ Assertions.assertEquals(0, env.size());
+ Assertions.assertEquals(1, prop.size());
+ Assertions.assertEquals("false",
prop.getProperty("camel.language.exchangeProperty.trim"));
+ }
+
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 443976a4fa07..639c5fdd6ee7 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -3886,6 +3886,58 @@ When `camel.opentelemetry.*` properties are configured
but OpenTelemetry is disa
the `camel.opentelemetry2.*` or `camel.telemetryDev.*` properties are now
used. Previously they were ignored whenever
any `camel.opentelemetry.*` property was configured.
+=== camel-main - `CAMEL_COMPONENT_*`, `CAMEL_DATAFORMAT_*`, `CAMEL_LANGUAGE_*`
environment variables now take effect
+
+Two bugs in `camel-main`'s environment-variable processing were fixed and are
now active.
+
+**Bug 1 — variables were silently ignored since Camel 3.9 (CAMEL-16345)**
+
+`filterEnvVariables` uppercases the variable names before comparing them
against the supplied prefixes, so the prefixes
+themselves must be uppercase. Before this fix the code passed lowercase
prefixes (`camel.component.`, etc.), causing
+every match to fail silently. This has been corrected: `CAMEL_COMPONENT_*`,
`CAMEL_DATAFORMAT_*` and `CAMEL_LANGUAGE_*`
+variables are now actually read.
+
+If your deployments intentionally set such variables but relied on them being
ignored (for example to pass secrets that
+should not reach the component), remove the variables or set
+`camel.main.autoConfigurationEnvironmentVariablesEnabled=false` to opt out
(note: this also disables `CAMEL_MAIN_*`
+and all other ENV-based configuration in `BaseMainSupport`).
+
+Variables that refer to a name not present on the classpath (for example
`CAMEL_COMPONENT_KAFKA_BROKERS` in an
+application that does not include camel-kafka) will fail the startup with an
`IllegalArgumentException`. Variables with
+an empty value (for example `CAMEL_COMPONENT_SEDA_QUEUE_SIZE=` from an unset
compose variable) will fail with
+_value is empty_. There is no per-variable opt-out: rename the variable,
remove it, or set
+`camel.main.autoConfigurationEnvironmentVariablesEnabled=false` (note: this
also disables `CAMEL_MAIN_*` and all other
+ENV-based configuration in `BaseMainSupport`).
+
+Secrets passed as component environment variables (for example
`CAMEL_COMPONENT_AWS2_S3_SECRET_KEY`) will be seen by
+the `prod`-profile security check as plain-text secrets and will fail the
startup. The
+`camel.component.aws2-s3.secret-key={{env:CAMEL_COMPONENT_AWS2_S3_SECRET_KEY}}`
placeholder form does not help —
+`filterEnvVariables` still picks up the original variable and binds its value
directly. To pass a secret without
+triggering the security check, either rename the environment variable to a
name that does not match the
+`CAMEL_COMPONENT_*` pattern and reference it with a placeholder (for example
+`camel.component.aws2-s3.secret-key={{env:MY_AWS_SECRET_KEY}}`), configure the
value through a vault, or disable
+auto-configuration from environment variables with
`camel.main.autoConfigurationEnvironmentVariablesEnabled=false`
+(which also turns off the `CAMEL_MAIN_*` variables).
+
+**Bug 2 — ambiguous name resolution**
+
+When the catalog contained component/dataformat/language pairs whose names
were prefixes of each other (such as `netty`
+/ `netty-http`, `js` / `jsonpath`, `avro` / `avroJackson`), the variable was
randomly mapped to either name.
+A longest-match strategy now always selects the most specific name.
+
+**Known limitation — `rest` / `rest-api` and custom-named instances**
+
+`CAMEL_COMPONENT_REST_API_DOC=api.json` is matched to
`camel.component.rest-api.doc` (the longer match), not to
+`camel.component.rest.apiDoc`. Use `CAMEL_COMPONENT_REST_APIDOC=api.json`
instead (option names are matched
+case-insensitively). Note that using
`camel.component.rest.apiDoc={{env:CAMEL_COMPONENT_REST_API_DOC}}` does not work
+as a workaround — `filterEnvVariables` still matches the original variable and
binds it to `rest-api.doc` in addition
+to the placeholder.
+
+The same limitation affects custom-named component instances whose name starts
with a catalog name. For example, with a
+component registered as `kafka-dr`, `CAMEL_COMPONENT_KAFKA_DR_BROKERS` is
matched to `kafka` (the catalog entry) and
+becomes `camel.component.kafka.dr-brokers`, an unknown option that fails the
startup. Configure such instances through
+`application.properties` instead:
`camel.component.kafka-dr.brokers={{env:KAFKA_DR_BROKERS}}`.
+
=== camel-seda - stopping a suspended route does not wait for its pending
messages
Stopping a suspended SEDA route, or stopping the CamelContext while such a
route is suspended, no longer waits for the