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:
+         * 
&lt;code&gt;org.apache.camel.component.statestore.StateStoreBackendFactory&lt;/code&gt;
 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;
             }
         }

Reply via email to