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 dfcc00d01fff CAMEL-25553: vault properties functions - make the shared
state thread safe (#27680)
dfcc00d01fff is described below
commit dfcc00d01fff688705b7086e62203be3889c39df
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 11 09:01:25 2026 +0200
CAMEL-25553: vault properties functions - make the shared state thread safe
(#27680)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../docs/ibm-secrets-manager-component.adoc | 1 +
.../ParameterStorePropertiesFunction.java | 8 +-
.../SecretsManagerPropertiesFunction.java | 8 +-
.../key/vault/KeyVaultPropertiesFunction.java | 8 +-
.../vault/CyberArkVaultPropertiesFunction.java | 8 +-
.../GoogleSecretManagerPropertiesFunction.java | 8 +-
.../main/docs/ibm-secrets-manager-component.adoc | 1 +
.../IBMSecretsManagerPropertiesFunction.java | 21 +++-
.../IBMSecretsManagerPropertiesFunctionTest.java | 137 +++++++++++++++++++++
.../config/SpringCloudConfigReloadTriggerTask.java | 6 +-
.../cloud/config/SpringConfigRemaindersCache.java | 13 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 7 ++
12 files changed, 202 insertions(+), 24 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
index fc7edff23397..43a9e69fa908 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
@@ -411,6 +411,7 @@ YAML::
====
Where route will be the name of the secret stored in the IBM Secrets Manager
Vault instance, in the 'default' secret group.
+The secret group can be omitted, such as `{{ibm:route}}`, and then the secret
is looked up in the 'default' secret group.
You could specify a default value in case the secret is not present on IBM
Secrets Manager Vault instance:
diff --git
a/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
b/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
index 799ef380c079..7fae03943c0f 100644
---
a/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
+++
b/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
@@ -17,8 +17,9 @@
package org.apache.camel.component.aws.parameterstore;
import java.net.URI;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
@@ -99,7 +100,8 @@ public class ParameterStorePropertiesFunction extends
ServiceSupport implements
private CamelContext camelContext;
private SsmClient client;
- private final Set<String> parameters = new HashSet<>();
+ // this function is shared and invoked concurrently
+ private final Set<String> parameters = ConcurrentHashMap.newKeySet();
private String region;
private boolean defaultCredentialsProvider;
@@ -278,7 +280,7 @@ public class ParameterStorePropertiesFunction extends
ServiceSupport implements
* Names of the parameters in use
*/
public Set<String> getParameters() {
- return parameters;
+ return Collections.unmodifiableSet(parameters);
}
/**
diff --git
a/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
b/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
index 8813a510e721..25643800649a 100644
---
a/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
+++
b/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
@@ -18,8 +18,9 @@ package org.apache.camel.component.aws.secretsmanager;
import java.net.URI;
import java.util.Base64;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -103,7 +104,8 @@ public class SecretsManagerPropertiesFunction extends
ServiceSupport implements
private CamelContext camelContext;
private SecretsManagerClient client;
- private final Set<String> secrets = new HashSet<>();
+ // this function is shared and invoked concurrently
+ private final Set<String> secrets = ConcurrentHashMap.newKeySet();
private String region;
private boolean defaultCredentialsProvider;
@@ -326,7 +328,7 @@ public class SecretsManagerPropertiesFunction extends
ServiceSupport implements
* Ids of the secrets in use
*/
public Set<String> getSecrets() {
- return secrets;
+ return Collections.unmodifiableSet(secrets);
}
/**
diff --git
a/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
b/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
index 97b04ef74835..8ae685120e18 100644
---
a/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
+++
b/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
@@ -16,8 +16,9 @@
*/
package org.apache.camel.component.azure.key.vault;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.azure.core.credential.TokenCredential;
import com.azure.core.exception.ResourceNotFoundException;
@@ -86,7 +87,8 @@ public class KeyVaultPropertiesFunction extends
ServiceSupport implements Proper
private static final String CAMEL_VAULT_AZURE_IDENTITY_ENABLED =
"CAMEL_VAULT_AZURE_IDENTITY_ENABLED";
private CamelContext camelContext;
private SecretClient client;
- private final Set<String> secrets = new HashSet<>();
+ // this function is shared and invoked concurrently
+ private final Set<String> secrets = ConcurrentHashMap.newKeySet();
public KeyVaultPropertiesFunction() {
super();
@@ -264,6 +266,6 @@ public class KeyVaultPropertiesFunction extends
ServiceSupport implements Proper
* Ids of the secrets in use
*/
public Set<String> getSecrets() {
- return secrets;
+ return Collections.unmodifiableSet(secrets);
}
}
diff --git
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
index 365bb2d5ba69..7ba9f35bdfba 100644
---
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
+++
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
@@ -16,8 +16,9 @@
*/
package org.apache.camel.component.cyberark.vault;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -82,7 +83,8 @@ public class CyberArkVaultPropertiesFunction extends
ServiceSupport implements P
private CamelContext camelContext;
private ConjurClient client;
- private final Set<String> secrets = new HashSet<>();
+ // this function is shared and invoked concurrently
+ private final Set<String> secrets = ConcurrentHashMap.newKeySet();
private String url;
private String account;
@@ -269,7 +271,7 @@ public class CyberArkVaultPropertiesFunction extends
ServiceSupport implements P
* Ids of the secrets in use
*/
public Set<String> getSecrets() {
- return secrets;
+ return Collections.unmodifiableSet(secrets);
}
/**
diff --git
a/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
b/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
index 7acba28405ed..1911f3b67534 100644
---
a/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
+++
b/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
@@ -17,8 +17,9 @@
package org.apache.camel.component.google.secret.manager;
import java.io.InputStream;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -86,7 +87,8 @@ public class GoogleSecretManagerPropertiesFunction extends
ServiceSupport implem
private CamelContext camelContext;
private SecretManagerServiceClient client;
private String projectId;
- private final Set<String> secrets = new HashSet<>();
+ // this function is shared and invoked concurrently
+ private final Set<String> secrets = ConcurrentHashMap.newKeySet();
public GoogleSecretManagerPropertiesFunction() {
super();
@@ -250,7 +252,7 @@ public class GoogleSecretManagerPropertiesFunction extends
ServiceSupport implem
* Ids of the secrets in use
*/
public Set<String> getSecrets() {
- return secrets;
+ return Collections.unmodifiableSet(secrets);
}
/**
diff --git
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
index fc7edff23397..43a9e69fa908 100644
---
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
+++
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
@@ -411,6 +411,7 @@ YAML::
====
Where route will be the name of the secret stored in the IBM Secrets Manager
Vault instance, in the 'default' secret group.
+The secret group can be omitted, such as `{{ibm:route}}`, and then the secret
is looked up in the 'default' secret group.
You could specify a default value in case the secret is not present on IBM
Secrets Manager Vault instance:
diff --git
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
index bf57267763fa..eadab7bb85f1 100644
---
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
+++
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
@@ -16,9 +16,10 @@
*/
package org.apache.camel.component.ibm.secrets.manager;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.ibm.cloud.sdk.core.http.Response;
import com.ibm.cloud.sdk.core.security.IamAuthenticator;
@@ -58,6 +59,7 @@ import
org.apache.camel.vault.IBMSecretsManagerVaultConfiguration;
* This implementation is to return the secret value associated with a key.
The properties related to this kind of
* Properties Function are all prefixed with <tt>ibm:</tt>. For example asking
for <tt>ibm:default:token</tt>, will
* return the secret value associated to the secret named token on IBM Secrets
Manager, in the Secret group "default".
+ * When the secret group is omitted, such as <tt>ibm:token</tt>, then the
secret group "default" is used.
*
* Another way of retrieving a secret value is using the following notation
<tt>ibm:default:database/username</tt>: in
* this case the field username of the secret database, in the secret group
"default", will be returned. As a fallback,
@@ -72,11 +74,16 @@ public class IBMSecretsManagerPropertiesFunction extends
ServiceSupport implemen
private static final String CAMEL_VAULT_IBM_TOKEN_ENV =
"CAMEL_VAULT_IBM_TOKEN";
private static final String CAMEL_VAULT_IBM_SERVICE_URL_ENV =
"CAMEL_VAULT_IBM_SERVICE_URL";
+ /**
+ * The secret group used when the lookup does not specify one (such as
<tt>ibm:token</tt>).
+ */
+ static final String DEFAULT_SECRET_GROUP = "default";
+
private CamelContext camelContext;
private SecretsManager client;
- private String secretGroup;
- private final Set<String> secrets = new HashSet<>();
+ // this function is shared and invoked concurrently
+ private final Set<String> secrets = ConcurrentHashMap.newKeySet();
public IBMSecretsManagerPropertiesFunction() {
super();
@@ -140,6 +147,8 @@ public class IBMSecretsManagerPropertiesFunction extends
ServiceSupport implemen
String returnValue = null;
String defaultValue = null;
String version = null;
+ // the secret group is per lookup (this function is shared by all
threads)
+ String secretGroup = DEFAULT_SECRET_GROUP;
if (remainder.contains("#")) {
String keyRemainder = StringHelper.before(remainder, "#");
secretGroup = StringHelper.before(keyRemainder, ":");
@@ -187,7 +196,7 @@ public class IBMSecretsManagerPropertiesFunction extends
ServiceSupport implemen
if (key != null) {
try {
- returnValue = getSecretFromSource(key, subkey, defaultValue,
version);
+ returnValue = getSecretFromSource(secretGroup, key, subkey,
defaultValue, version);
} catch (Exception e) {
throw new RuntimeCamelException(
"Error getting secret from vault using key: " + key +
" due to: " + e.getMessage(), e);
@@ -198,7 +207,7 @@ public class IBMSecretsManagerPropertiesFunction extends
ServiceSupport implemen
}
private String getSecretFromSource(
- String key, String subkey, String defaultValue, String version) {
+ String secretGroup, String key, String subkey, String
defaultValue, String version) {
// capture name of secret
secrets.add(key);
String returnValue = "";
@@ -273,6 +282,6 @@ public class IBMSecretsManagerPropertiesFunction extends
ServiceSupport implemen
* Ids of the secrets in use
*/
public Set<String> getSecrets() {
- return secrets;
+ return Collections.unmodifiableSet(secrets);
}
}
diff --git
a/components/camel-ibm/camel-ibm-secrets-manager/src/test/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunctionTest.java
b/components/camel-ibm/camel-ibm-secrets-manager/src/test/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunctionTest.java
new file mode 100644
index 000000000000..f17a7d213a93
--- /dev/null
+++
b/components/camel-ibm/camel-ibm-secrets-manager/src/test/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunctionTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.ibm.secrets.manager;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.ibm.cloud.sdk.core.http.Response;
+import com.ibm.cloud.sdk.core.http.ServiceCall;
+import com.ibm.cloud.sdk.core.http.ServiceCallback;
+import com.ibm.cloud.sdk.core.security.NoAuthAuthenticator;
+import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.SecretsManager;
+import
com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.GetSecretByNameTypeOptions;
+import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.Secret;
+import io.reactivex.Single;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class IBMSecretsManagerPropertiesFunctionTest {
+
+ @Test
+ public void testSecretGroupIsPerLookup() {
+ RecordingSecretsManager client = new RecordingSecretsManager();
+ IBMSecretsManagerPropertiesFunction function = new
IBMSecretsManagerPropertiesFunction(client);
+
+ assertEquals("groupA/route", function.apply("groupA:route"));
+ assertEquals("groupB-user",
function.apply("groupB:database#username"));
+ // no group must not reuse the group of the previous lookup
+ assertEquals("default/route", function.apply("route"));
+ assertEquals("groupA/route", function.apply("groupA:route:fallback"));
+
+ assertEquals(List.of("groupA", "groupB", "default", "groupA"),
client.groups);
+ assertEquals(Set.of("route", "database"), function.getSecrets());
+ }
+
+ @Test
+ public void testSecretsIsReadOnlyView() {
+ IBMSecretsManagerPropertiesFunction function = new
IBMSecretsManagerPropertiesFunction(new RecordingSecretsManager());
+ function.apply("groupA:route");
+
+ Set<String> secrets = function.getSecrets();
+ assertThrows(UnsupportedOperationException.class, () ->
secrets.add("other"));
+
+ // the view reflects later lookups
+ function.apply("groupA:other");
+ assertTrue(secrets.contains("other"));
+ }
+
+ /**
+ * Returns a secret whose payload and data are derived from the requested
secret group and name.
+ */
+ private static final class RecordingSecretsManager extends SecretsManager {
+
+ private final List<String> groups = new CopyOnWriteArrayList<>();
+
+ RecordingSecretsManager() {
+ super("test", new NoAuthAuthenticator());
+ }
+
+ @Override
+ public ServiceCall<Secret>
getSecretByNameType(GetSecretByNameTypeOptions options) {
+ String group = options.secretGroupName();
+ groups.add(group);
+ Secret secret = new TestSecret(group + "/" + options.name(),
Map.of("username", group + "-user"));
+ okhttp3.Response http = new okhttp3.Response.Builder()
+ .request(new
Request.Builder().url("http://localhost/").build())
+ .protocol(Protocol.HTTP_1_1)
+ .code(200)
+ .message("OK")
+ .build();
+ return new FixedServiceCall<>(new Response<>(secret, http));
+ }
+ }
+
+ private static final class TestSecret extends Secret {
+
+ TestSecret(String payload, Map<String, Object> data) {
+ this.payload = payload;
+ this.data = data;
+ }
+ }
+
+ private static final class FixedServiceCall<T> implements ServiceCall<T> {
+
+ private final Response<T> response;
+
+ FixedServiceCall(Response<T> response) {
+ this.response = response;
+ }
+
+ @Override
+ public ServiceCall<T> addHeader(String name, String value) {
+ return this;
+ }
+
+ @Override
+ public Response<T> execute() {
+ return response;
+ }
+
+ @Override
+ public void enqueue(ServiceCallback<T> callback) {
+ callback.onResponse(response);
+ }
+
+ @Override
+ public Single<Response<T>> reactiveRequest() {
+ return Single.just(response);
+ }
+
+ @Override
+ public void cancel() {
+ // noop
+ }
+ }
+}
diff --git
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
index 29d6da71ed81..e35fd9872f73 100644
---
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
+++
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
@@ -46,13 +46,15 @@ public class SpringCloudConfigReloadTriggerTask extends
ServiceSupport implement
@Override
public void run() {
- if (SpringConfigRemaindersCache.get().isEmpty()) {
+ // iterate a copy as the properties function can add entries
concurrently
+ Map<String, String> remainders =
SpringConfigRemaindersCache.snapshot();
+ if (remainders.isEmpty()) {
return;
}
ConfigData configData = new
SpringConfigProvider().getConfigData(getCamelContext());
- for (Map.Entry<String, String> entry :
SpringConfigRemaindersCache.get().entrySet()) {
+ for (Map.Entry<String, String> entry : remainders.entrySet()) {
for (PropertySource propertySource :
configData.getPropertySources()) {
if (propertySource.containsProperty(entry.getKey())
&&
!propertySource.getProperty(entry.getKey()).equals(entry.getValue())) {
diff --git
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
index e365d1e6d3cb..ac04cf55644c 100644
---
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
+++
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.spring.cloud.config;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -24,7 +25,8 @@ public class SpringConfigRemaindersCache {
private SpringConfigRemaindersCache() {
}
- private static final Map<String, String> REMAINDERS_CACHE = new
HashMap<>();
+ // written by the properties function (any thread) and read by the reload
task; values can be null
+ private static final Map<String, String> REMAINDERS_CACHE =
Collections.synchronizedMap(new HashMap<>());
public static Map<String, String> get() {
return REMAINDERS_CACHE;
@@ -33,4 +35,13 @@ public class SpringConfigRemaindersCache {
public static void put(String remainder, String value) {
REMAINDERS_CACHE.put(remainder, value);
}
+
+ /**
+ * A copy of the cache that is safe to iterate while properties are being
resolved.
+ */
+ public static Map<String, String> snapshot() {
+ synchronized (REMAINDERS_CACHE) {
+ return new HashMap<>(REMAINDERS_CACHE);
+ }
+ }
}
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 e79e7ed43dfe..c2568bcd695c 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
@@ -2687,6 +2687,13 @@ breaking change for routes that reference the header by
its literal string name:
`CamelIBMCOSCacheControl`, although the change is trivial to adapt. Routes
using the
`IBMCOSConstants.CACHE_CONTROL` constant are unaffected.
+=== camel-ibm-secrets-manager - a lookup without a secret group uses the
default group
+
+The `ibm:` properties function now uses the `default` secret group when the
lookup does not specify one, such as
+`{{ibm:route}}`. Previously such a lookup failed when it was the first one,
and otherwise reused the secret group of
+whichever lookup ran before it (possibly from another thread). Specify the
group, such as `{{ibm:mygroup:route}}`,
+to read a secret from another group.
+
=== camel-knative
The Knative HTTP consumer no longer returns the stack trace of a failed
exchange to the caller.