diff --git a/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java index 25b02114f798..b32f0c13ee43 100644 --- a/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java +++ b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java @@ -18,11 +18,11 @@ import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.aws.secretsmanager.SecretsManagerPropertiesFunction; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -80,23 +80,21 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{aws:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { String element = secretsManagerPropertiesFunction.apply(stringValue .replace("{{aws:", "") .replace("}}", "")); - props.put(key, element); + EarlyResolutionPropertySources.putIfAbsent(props, key, element); } catch (Exception e) { if (ignoreResolutionFailures) { LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " diff --git a/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java b/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java index f240c1cd0132..7655f90836f0 100644 --- a/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java +++ b/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java @@ -24,11 +24,11 @@ import com.azure.security.keyvault.secrets.SecretClientBuilder; import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.azure.key.vault.KeyVaultPropertiesFunction; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -90,22 +90,20 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{azure:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { String element = keyVaultPropertiesFunction.apply(stringValue .replace("{{azure:", "") .replace("}}", "")); - props.put(key, element); + EarlyResolutionPropertySources.putIfAbsent(props, key, element); } catch (Exception e) { if (ignoreResolutionFailures) { LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " diff --git a/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java b/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java index 4242da15ea29..cb1bcff17ef1 100644 --- a/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java +++ b/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java @@ -20,11 +20,11 @@ import org.apache.camel.component.cyberark.vault.CyberArkVaultPropertiesFunction; import org.apache.camel.component.cyberark.vault.client.ConjurClient; import org.apache.camel.component.cyberark.vault.client.ConjurClientFactory; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -78,23 +78,21 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{cyberark:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { String element = cyberArkVaultPropertiesFunction.apply(stringValue .replace("{{cyberark:", "") .replace("}}", "")); - props.put(key, element); + EarlyResolutionPropertySources.putIfAbsent(props, key, element); } catch (Exception e) { if (ignoreResolutionFailures) { LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " diff --git a/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java b/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java index 696c9c4d0de6..25016a92b8ea 100644 --- a/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java +++ b/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java @@ -20,11 +20,11 @@ import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.google.secret.manager.GoogleSecretManagerPropertiesFunction; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -66,22 +66,20 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{gcp:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { String element = secretsManagerPropertiesFunction.apply(stringValue .replace("{{gcp:", "") .replace("}}", "")); - props.put(key, element); + EarlyResolutionPropertySources.putIfAbsent(props, key, element); } catch (Exception e) { if (ignoreResolutionFailures) { LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " diff --git a/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java index eb930f40d1f0..5676c4f45b67 100644 --- a/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java +++ b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java @@ -18,10 +18,10 @@ import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.hashicorp.vault.HashicorpVaultPropertiesFunction; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -70,20 +70,18 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{hashicorp:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { - props.put(key, hashicorpVaultPropertiesFunction.apply(stringValue + EarlyResolutionPropertySources.putIfAbsent(props, key, hashicorpVaultPropertiesFunction.apply(stringValue .replace("{{hashicorp:", "") .replace("}}", ""))); } catch (Exception e) { diff --git a/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParserPrecedenceTest.java b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParserPrecedenceTest.java new file mode 100644 index 000000000000..a7d71c722ca5 --- /dev/null +++ b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParserPrecedenceTest.java @@ -0,0 +1,69 @@ +/* + * 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.hashicorp.vault.springboot; + +import java.util.Map; + +import org.apache.camel.test.infra.hashicorp.vault.services.HashicorpServiceFactory; +import org.apache.camel.test.infra.hashicorp.vault.services.HashicorpVaultService; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.bootstrap.DefaultBootstrapContext; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +class SpringBootHashicorpVaultPropertiesParserPrecedenceTest { + + @RegisterExtension + static HashicorpVaultService service = HashicorpServiceFactory.createService(); + + @BeforeAll + static void setupVaultConnection() { + System.setProperty("camel.vault.hashicorp.host", service.host()); + System.setProperty("camel.vault.hashicorp.port", String.valueOf(service.port())); + System.setProperty("camel.vault.hashicorp.scheme", "http"); + System.setProperty("camel.vault.hashicorp.token", service.token()); + } + + @Test + void shouldSkipMissingLowerPrecedencePlaceholderWhenHigherPrecedenceDefinesPlainValue() { + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addFirst(new MapPropertySource("config", Map.of( + "camel.component.hashicorp-vault.early-resolve-properties", "true", + "camel.vault.hashicorp.host", service.host(), + "camel.vault.hashicorp.port", String.valueOf(service.port()), + "camel.vault.hashicorp.scheme", "http", + "camel.vault.hashicorp.token", service.token()))); + environment.getPropertySources().addFirst(new MapPropertySource("precedence-high", Map.of( + "precedence.test.key", "plain-value-from-high"))); + environment.getPropertySources().addLast(new MapPropertySource("precedence-low", Map.of( + "precedence.test.key", "{{hashicorp:does-not-exist#field}}"))); + + SpringBootHashicorpVaultPropertiesParser parser = new SpringBootHashicorpVaultPropertiesParser(); + ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent( + new DefaultBootstrapContext(), new SpringApplication(Object.class), new String[] {}, environment); + + assertThatCode(() -> parser.onApplicationEvent(event)).doesNotThrowAnyException(); + assertThat(environment.getProperty("precedence.test.key")).isEqualTo("plain-value-from-high"); + } +} diff --git a/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java index 5bca3dc9ef86..688ac2151583 100644 --- a/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java +++ b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java @@ -20,11 +20,11 @@ import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.SecretsManager; import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.ibm.secrets.manager.IBMSecretsManagerPropertiesFunction; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -64,22 +64,20 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{ibm:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { String element = secretsManagerPropertiesFunction.apply(stringValue .replace("{{ibm:", "") .replace("}}", "")); - props.put(key, element); + EarlyResolutionPropertySources.putIfAbsent(props, key, element); } catch (Exception e) { if (ignoreResolutionFailures) { LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " diff --git a/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/SpringBootJasyptPropertiesParser.java b/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/SpringBootJasyptPropertiesParser.java index b75bde48a512..daac0659a908 100644 --- a/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/SpringBootJasyptPropertiesParser.java +++ b/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/SpringBootJasyptPropertiesParser.java @@ -18,6 +18,7 @@ import org.apache.camel.component.jasypt.JasyptPropertiesParser; import org.apache.camel.component.properties.PropertiesParser; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StringHelper; import org.jasypt.encryption.StringEncryptor; @@ -25,7 +26,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -70,14 +70,19 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - if (value instanceof OriginTrackedValue originTrackedValue && - originTrackedValue.getValue() instanceof String stringValue && - stringValue.startsWith(JasyptPropertiesParser.JASYPT_PREFIX_TOKEN) && - stringValue.endsWith(JasyptPropertiesParser.JASYPT_SUFFIX_TOKEN)) { + String stringValue = EarlyResolutionPropertySources.asString(value); + if (stringValue != null + && stringValue.startsWith(JasyptPropertiesParser.JASYPT_PREFIX_TOKEN) + && stringValue.endsWith(JasyptPropertiesParser.JASYPT_SUFFIX_TOKEN)) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { - props.put(key, propertiesParser.parseProperty(key.toString(), stringValue, null)); + EarlyResolutionPropertySources.putIfAbsent(props, key, + propertiesParser.parseProperty(key.toString(), stringValue, null)); } catch (Exception e) { // Log and do nothing LOG.debug("failed to parse property {}", key, e); diff --git a/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java b/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java index d2c951b90238..734102d5016f 100644 --- a/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java +++ b/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java @@ -18,10 +18,10 @@ import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.spring.cloud.config.SpringCloudConfigPropertiesFunction; +import org.apache.camel.spring.boot.EarlyResolutionPropertySources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -49,20 +49,18 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { mapPropertySource.getSource().forEach((key, value) -> { - String stringValue = null; - if ((value instanceof OriginTrackedValue originTrackedValue - && originTrackedValue.getValue() instanceof String v)) { - stringValue = v; - } else if (value instanceof String v) { - stringValue = v; - } + String stringValue = EarlyResolutionPropertySources.asString(value); if (stringValue != null && stringValue.startsWith("{{spring-config:") && stringValue.endsWith("}}")) { + if (EarlyResolutionPropertySources.shouldSkipBecauseHigherPrecedenceDefines( + event.getEnvironment().getPropertySources(), mutablePropertySources, key, LOG)) { + return; + } LOG.debug("decrypting and overriding property {}", key); try { String element = springCloudConfigPropertiesFunction .apply(stringValue.replace("{{spring-config:", "").replace("}}", "")); - properties.put(key, element); + EarlyResolutionPropertySources.putIfAbsent(properties, key, element); } catch (Exception e) { if (ignoreResolutionFailures) { LOG.warn("Failed to resolve property {} from Spring Cloud Config; the placeholder is left " diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/EarlyResolutionPropertySources.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/EarlyResolutionPropertySources.java new file mode 100644 index 000000000000..6b2fabecad41 --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/EarlyResolutionPropertySources.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.spring.boot; + +import java.util.Properties; + +import org.slf4j.Logger; +import org.springframework.boot.origin.OriginTrackedValue; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; + +/** + * Helpers for early property resolution listeners that inject a flat {@link Properties} override via + * {@code PropertySources.addFirst}. + *
+ * Spring iterates property sources in precedence order (highest first). When the same key appears in multiple + * sources, callers must retain the first resolved value so the override map reflects Spring's normal precedence + * after it is added with {@code addFirst}. + */ +public final class EarlyResolutionPropertySources { + + private EarlyResolutionPropertySources() { + } + + /** + * Returns the string value from a property source entry, including {@link OriginTrackedValue} wrappers. + */ + public static String asString(Object value) { + if (value instanceof OriginTrackedValue originTrackedValue + && originTrackedValue.getValue() instanceof String stringValue) { + return stringValue; + } + if (value instanceof String stringValue) { + return stringValue; + } + return null; + } + + /** + * Stores a resolved value only when the key is not already present, preserving highest-precedence values + * collected while iterating property sources. + */ + public static void putIfAbsent(Properties props, Object key, String resolvedValue) { + props.putIfAbsent(key.toString(), resolvedValue); + } + + /** + * Returns whether a property source with higher precedence defines the same key. + */ + public static boolean hasHigherPrecedenceProperty( + Iterable