This is an automated email from the ASF dual-hosted git repository.
Croway 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 524a7d6e0534 CAMEL-25791: camel-state-store - Add backend factory
option to create one KeyValueRepository per store name (#28086)
524a7d6e0534 is described below
commit 524a7d6e0534f5df8b3435207be96b46154f9678
Author: Federico Mariani <[email protected]>
AuthorDate: Mon Oct 19 13:33:06 2026 +0200
CAMEL-25791: camel-state-store - Add backend factory option to create one
KeyValueRepository per store name (#28086)
* CAMEL-25791: camel-state-store - Add backend factory option to create one
KeyValueRepository per store name
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CAMEL-25791: document that ambiguous backend factories are not autowired
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CAMEL-25791: warn when several backend factories are found, fix Javadoc
tags
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../camel/catalog/components/state-store.json | 3 +-
.../camel/catalog/docs/state-store-component.adoc | 19 ++++
.../statestore/StateStoreComponentConfigurer.java | 11 +++
.../camel/component/statestore/state-store.json | 3 +-
.../src/main/docs/state-store-component.adoc | 19 ++++
.../statestore/StateStoreBackendFactory.java | 39 ++++++++
.../component/statestore/StateStoreComponent.java | 33 ++++++-
.../StateStoreBackendFactoryAmbiguousTest.java | 105 +++++++++++++++++++++
.../statestore/StateStoreBackendFactoryTest.java | 97 +++++++++++++++++++
.../dsl/StateStoreComponentBuilderFactory.java | 19 ++++
10 files changed, 345 insertions(+), 3 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
index a32de27abff4..47d003a36392 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
@@ -25,7 +25,8 @@
},
"componentProperties": {
"lazyStartProducer": { "index": 0, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
- "autowiredEnabled": { "index": 1, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "autowiredEnabled": { "index": 1, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "backendFactory": { "index": 2, "kind": "property", "displayName":
"Backend Factory", "group": "advanced", "label": "advanced", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.statestore.StateStoreBackendFactory", "deprecated":
false, "autowired": true, "secret": false, "description": "Factory that creates
a dedicated backend for each store name. When set, it takes precedence over
registry auto-discovery for endpoints without an explicit backend." }
},
"headers": {
"CamelStateStoreOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put",
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ],
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation to perform", "constantName":
"org.apache.camel.component.statestore.StateSt [...]
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
index 23b058227383..d43032196fa4 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
@@ -163,6 +163,25 @@ endpoints. If none is found, the default in-memory backend
(`MemoryKeyValueRepos
This means you can configure a backend once (via Java or properties) and all
`state-store`
endpoints will use it without needing `backend=#beanName` on each endpoint URI.
+=== Backend per store name
+
+To give each store name its own backend, set the `backendFactory` component
option to a
+`StateStoreBackendFactory`. A single bean of that type in the registry is
autowired. The factory
+is called once per store name and takes precedence over the registry lookup
above. The component
+starts and stops the backends it creates. If the factory returns `null`, the
usual resolution applies.
+
+[source,java]
+----
+StateStoreComponent stateStore = context.getComponent("state-store",
StateStoreComponent.class);
+stateStore.setBackendFactory(storeName -> new MyKeyValueRepository(storeName));
+----
+
+An explicit `backend` option on the endpoint still takes precedence over the
factory.
+
+NOTE: Autowiring only applies when the registry holds exactly one
`StateStoreBackendFactory`. With two or
+more, none is autowired: the component logs a WARN and falls back to the
default resolution. Set
+`backendFactory` explicitly to choose one.
+
=== In-Memory (default)
The default backend (`MemoryKeyValueRepository`) stores entries in a
`ConcurrentHashMap`. TTL
diff --git
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
index fa7fc5815700..cbbb7a8e96a0 100644
---
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
+++
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
@@ -25,17 +25,26 @@ public class StateStoreComponentConfigurer extends
PropertyConfigurerSupport imp
switch (ignoreCase ? name.toLowerCase() : name) {
case "autowiredenabled":
case "autowiredEnabled":
target.setAutowiredEnabled(property(camelContext, boolean.class, value));
return true;
+ case "backendfactory":
+ case "backendFactory": target.setBackendFactory(property(camelContext,
org.apache.camel.component.statestore.StateStoreBackendFactory.class, value));
return true;
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
default: return false;
}
}
+ @Override
+ public String[] getAutowiredNames() {
+ return new String[]{"backendFactory"};
+ }
+
@Override
public Class<?> getOptionType(String name, boolean ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
case "autowiredenabled":
case "autowiredEnabled": return boolean.class;
+ case "backendfactory":
+ case "backendFactory": return
org.apache.camel.component.statestore.StateStoreBackendFactory.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
default: return null;
@@ -48,6 +57,8 @@ public class StateStoreComponentConfigurer extends
PropertyConfigurerSupport imp
switch (ignoreCase ? name.toLowerCase() : name) {
case "autowiredenabled":
case "autowiredEnabled": return target.isAutowiredEnabled();
+ case "backendfactory":
+ case "backendFactory": return target.getBackendFactory();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
default: return null;
diff --git
a/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
b/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
index a32de27abff4..47d003a36392 100644
---
a/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
+++
b/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
@@ -25,7 +25,8 @@
},
"componentProperties": {
"lazyStartProducer": { "index": 0, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
- "autowiredEnabled": { "index": 1, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "autowiredEnabled": { "index": 1, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "backendFactory": { "index": 2, "kind": "property", "displayName":
"Backend Factory", "group": "advanced", "label": "advanced", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.statestore.StateStoreBackendFactory", "deprecated":
false, "autowired": true, "secret": false, "description": "Factory that creates
a dedicated backend for each store name. When set, it takes precedence over
registry auto-discovery for endpoints without an explicit backend." }
},
"headers": {
"CamelStateStoreOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put",
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ],
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation to perform", "constantName":
"org.apache.camel.component.statestore.StateSt [...]
diff --git
a/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
b/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
index 23b058227383..d43032196fa4 100644
---
a/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
+++
b/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
@@ -163,6 +163,25 @@ endpoints. If none is found, the default in-memory backend
(`MemoryKeyValueRepos
This means you can configure a backend once (via Java or properties) and all
`state-store`
endpoints will use it without needing `backend=#beanName` on each endpoint URI.
+=== Backend per store name
+
+To give each store name its own backend, set the `backendFactory` component
option to a
+`StateStoreBackendFactory`. A single bean of that type in the registry is
autowired. The factory
+is called once per store name and takes precedence over the registry lookup
above. The component
+starts and stops the backends it creates. If the factory returns `null`, the
usual resolution applies.
+
+[source,java]
+----
+StateStoreComponent stateStore = context.getComponent("state-store",
StateStoreComponent.class);
+stateStore.setBackendFactory(storeName -> new MyKeyValueRepository(storeName));
+----
+
+An explicit `backend` option on the endpoint still takes precedence over the
factory.
+
+NOTE: Autowiring only applies when the registry holds exactly one
`StateStoreBackendFactory`. With two or
+more, none is autowired: the component logs a WARN and falls back to the
default resolution. Set
+`backendFactory` explicitly to choose one.
+
=== In-Memory (default)
The default backend (`MemoryKeyValueRepository`) stores entries in a
`ConcurrentHashMap`. TTL
diff --git
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreBackendFactory.java
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreBackendFactory.java
new file mode 100644
index 000000000000..53d76951c0da
--- /dev/null
+++
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreBackendFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.statestore;
+
+import org.apache.camel.spi.KeyValueRepository;
+
+/**
+ * Creates the {@link KeyValueRepository} backend for a given state store name.
+ * <p>
+ * Use this when each store name should map to its own backend instance, for
example a separate cache per store. The
+ * returned backend is owned by the {@link StateStoreComponent}, which starts
and stops it.
+ *
+ * @since 4.24
+ */
+@FunctionalInterface
+public interface StateStoreBackendFactory {
+
+ /**
+ * Creates the backend for the given store name.
+ *
+ * @param storeName the name of the state store, as used in the endpoint
URI
+ * @return the backend for the store, or {@code null} to fall
back to the default backend resolution
+ */
+ KeyValueRepository createBackend(String storeName);
+}
diff --git
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
index ca7dc43d56f4..7f466e69f19b 100644
---
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
+++
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
@@ -19,9 +19,11 @@ package org.apache.camel.component.statestore;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.Endpoint;
import org.apache.camel.spi.KeyValueRepository;
+import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
import org.apache.camel.support.MemoryKeyValueRepository;
@@ -40,6 +42,12 @@ public class StateStoreComponent extends DefaultComponent {
private static final Logger LOG =
LoggerFactory.getLogger(StateStoreComponent.class);
private final ConcurrentHashMap<String, KeyValueRepository> backends = new
ConcurrentHashMap<>();
+ private final AtomicBoolean backendFactoryChecked = new AtomicBoolean();
+
+ @Metadata(label = "advanced", autowired = true,
+ description = "Factory that creates a dedicated backend for each
store name. When set, it takes precedence over"
+ + " registry auto-discovery for endpoints without
an explicit backend.")
+ private StateStoreBackendFactory backendFactory;
@Override
protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) throws Exception {
@@ -68,9 +76,24 @@ public class StateStoreComponent extends DefaultComponent {
return explicitBackend;
}
return backends.computeIfAbsent(storeName, k -> {
+ if (backendFactory == null &&
backendFactoryChecked.compareAndSet(false, true)) {
+ // checked lazily as autowiring happens after the component is
initialized
+ int count =
getCamelContext().getRegistry().findByType(StateStoreBackendFactory.class).size();
+ if (count > 1) {
+ LOG.warn("Found {} StateStoreBackendFactory instances in
the registry. None is autowired;"
+ + " set the backendFactory option to choose one.",
+ count);
+ }
+ }
+ KeyValueRepository backend = backendFactory != null ?
backendFactory.createBackend(storeName) : null;
+ if (backend != null) {
+ LOG.debug("Created KeyValueRepository '{}' for store '{}'
using backend factory",
+ backend.getClass().getSimpleName(), storeName);
+ ServiceHelper.startService(backend);
+ return backend;
+ }
// Auto-discover a KeyValueRepository from the registry
Set<KeyValueRepository> found =
getCamelContext().getRegistry().findByType(KeyValueRepository.class);
- KeyValueRepository backend;
if (found.size() == 1) {
backend = found.iterator().next();
LOG.debug("Auto-discovered KeyValueRepository '{}' for store
'{}'",
@@ -93,6 +116,14 @@ public class StateStoreComponent extends DefaultComponent {
});
}
+ public StateStoreBackendFactory getBackendFactory() {
+ return backendFactory;
+ }
+
+ public void setBackendFactory(StateStoreBackendFactory backendFactory) {
+ this.backendFactory = backendFactory;
+ }
+
@Override
protected void doStop() throws Exception {
Exception firstException = null;
diff --git
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryAmbiguousTest.java
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryAmbiguousTest.java
new file mode 100644
index 000000000000..60ffea55b252
--- /dev/null
+++
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryAmbiguousTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.statestore;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.support.MemoryKeyValueRepository;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests that two {@link StateStoreBackendFactory} beans in the registry are
not autowired, so the component logs a WARN
+ * and falls back to the default backend resolution.
+ */
+class StateStoreBackendFactoryAmbiguousTest extends CamelTestSupport {
+
+ private final Set<String> calls = ConcurrentHashMap.newKeySet();
+ private final List<LogEvent> warnings = new CopyOnWriteArrayList<>();
+ private final Logger logger = (Logger)
LogManager.getLogger(StateStoreComponent.class);
+ private final AbstractAppender appender = new AbstractAppender("capture",
null, null, true, Property.EMPTY_ARRAY) {
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel() == Level.WARN) {
+ warnings.add(event.toImmutable());
+ }
+ }
+ };
+
+ private Level previousLevel;
+
+ @BeforeEach
+ void attachAppender() {
+ // the default log4j2 configuration only logs ERROR
+ previousLevel = logger.getLevel();
+ Configurator.setLevel(logger.getName(), Level.WARN);
+ appender.start();
+ logger.addAppender(appender);
+ }
+
+ @AfterEach
+ void detachAppender() {
+ logger.removeAppender(appender);
+ appender.stop();
+ Configurator.setLevel(logger.getName(), previousLevel);
+ }
+
+ @BindToRegistry("factory1")
+ public StateStoreBackendFactory factory1() {
+ return storeName -> {
+ calls.add("factory1:" + storeName);
+ return new MemoryKeyValueRepository();
+ };
+ }
+
+ @BindToRegistry("factory2")
+ public StateStoreBackendFactory factory2() {
+ return storeName -> {
+ calls.add("factory2:" + storeName);
+ return new MemoryKeyValueRepository();
+ };
+ }
+
+ @Test
+ void testTwoFactoriesAreNotAutowired() {
+ template.requestBodyAndHeader("state-store:orders?operation=put",
"order-1", StateStoreConstants.KEY, "1");
+
+
assertThat(template.requestBodyAndHeader("state-store:orders?operation=get",
null, StateStoreConstants.KEY, "1"))
+ .isEqualTo("order-1");
+ assertThat(context.getComponent("state-store",
StateStoreComponent.class).getBackendFactory()).isNull();
+ assertThat(calls).isEmpty();
+ assertThat(warnings).singleElement()
+ .extracting(e -> e.getMessage().getFormattedMessage())
+ .asString()
+ .contains("Found 2 StateStoreBackendFactory instances");
+ }
+}
diff --git
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
new file mode 100644
index 000000000000..c35b4cc723b1
--- /dev/null
+++
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.statestore;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.KeyValueRepository;
+import org.apache.camel.support.MemoryKeyValueRepository;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests that an autowired {@link StateStoreBackendFactory} creates one
backend per store name, takes precedence over a
+ * {@link KeyValueRepository} in the registry, and does not override an
explicit endpoint backend.
+ */
+class StateStoreBackendFactoryTest extends CamelTestSupport {
+
+ private final Map<String, MemoryKeyValueRepository> created = new
ConcurrentHashMap<>();
+ private final MemoryKeyValueRepository registryBackend = new
MemoryKeyValueRepository();
+ private final MemoryKeyValueRepository explicitBackend = new
MemoryKeyValueRepository();
+
+ @BindToRegistry("factory")
+ public StateStoreBackendFactory backendFactory() {
+ return storeName -> "fallback".equals(storeName)
+ ? null : created.computeIfAbsent(storeName, k -> new
MemoryKeyValueRepository());
+ }
+
+ @BindToRegistry("registryBackend")
+ public KeyValueRepository registryBackend() {
+ return registryBackend;
+ }
+
+ @Test
+ void testBackendPerStoreName() {
+ assertThat(context.getComponent("state-store",
StateStoreComponent.class).getBackendFactory()).isNotNull();
+
+ template.requestBodyAndHeader("state-store:orders?operation=put",
"order-1", StateStoreConstants.KEY, "1");
+ template.requestBodyAndHeader("state-store:users?operation=put",
"user-1", StateStoreConstants.KEY, "1");
+
+
assertThat(template.requestBodyAndHeader("state-store:orders?operation=get",
null, StateStoreConstants.KEY, "1"))
+ .isEqualTo("order-1");
+
assertThat(template.requestBodyAndHeader("state-store:users?operation=get",
null, StateStoreConstants.KEY, "1"))
+ .isEqualTo("user-1");
+
+ assertThat(created).containsOnlyKeys("orders", "users");
+ assertThat(created.get("orders").isStarted()).isTrue();
+ assertThat(registryBackend.keys()).isEmpty();
+ }
+
+ @Test
+ void testFactoryReturningNullFallsBackToRegistry() {
+ template.requestBodyAndHeader("state-store:fallback?operation=put",
"v", StateStoreConstants.KEY, "k");
+
+ assertThat(created).doesNotContainKey("fallback");
+ assertThat(registryBackend.get("k")).isEqualTo("v");
+ }
+
+ @Test
+ void testExplicitBackendTakesPrecedence() {
+ template.requestBodyAndHeader("direct:explicit", "v",
StateStoreConstants.KEY, "k");
+
+ assertThat(created).doesNotContainKey("explicit");
+ assertThat(explicitBackend.get("k")).isEqualTo("v");
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ StateStoreEndpoint explicit
+ =
getContext().getEndpoint("state-store:explicit?operation=put",
StateStoreEndpoint.class);
+ explicit.setBackend(explicitBackend);
+ from("direct:explicit").to(explicit);
+ }
+ };
+ }
+}
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
index cb2444613756..99ce12088646 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
@@ -97,6 +97,24 @@ public interface StateStoreComponentBuilderFactory {
doSetProperty("autowiredEnabled", autowiredEnabled);
return this;
}
+
+ /**
+ * Factory that creates a dedicated backend for each store name. When
+ * set, it takes precedence over registry auto-discovery for endpoints
+ * without an explicit backend.
+ *
+ * The option is a:
+ *
<code>org.apache.camel.component.statestore.StateStoreBackendFactory</code>
type.
+ *
+ * Group: advanced
+ *
+ * @param backendFactory the value to set
+ * @return the dsl builder
+ */
+ default StateStoreComponentBuilder
backendFactory(org.apache.camel.component.statestore.StateStoreBackendFactory
backendFactory) {
+ doSetProperty("backendFactory", backendFactory);
+ return this;
+ }
}
class StateStoreComponentBuilderImpl
@@ -114,6 +132,7 @@ public interface StateStoreComponentBuilderFactory {
switch (name) {
case "lazyStartProducer": ((StateStoreComponent)
component).setLazyStartProducer((boolean) value); return true;
case "autowiredEnabled": ((StateStoreComponent)
component).setAutowiredEnabled((boolean) value); return true;
+ case "backendFactory": ((StateStoreComponent)
component).setBackendFactory((org.apache.camel.component.statestore.StateStoreBackendFactory)
value); return true;
default: return false;
}
}