This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch feature/CAMEL-22616-cluster-config
in repository https://gitbox.apache.org/repos/asf/camel.git

commit f1da6c64b46072df7743accf5e2db1a9e3e27875
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 6 15:36:45 2026 +0200

    CAMEL-22616: camel-main - Add support for camel.cluster.xxx configurations
    
    Add declarative property-based configuration for cluster services in
    camel-main/camel-jbang, matching the existing Spring Boot behavior.
    
    Users can now configure cluster services via application.properties:
      camel.cluster.file.root=/tmp/cluster
      camel.cluster.kubernetes.namespace=default
    
    Each cluster service class (file, kubernetes, zookeeper, consul,
    jgroups-raft, infinispan) gets a @JdkService annotation for
    FactoryFinder-based classpath discovery. BaseMainSupport routes
    camel.cluster.* properties, groups by type sub-prefix, resolves the
    matching service, binds properties, and adds it to the CamelContext.
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../consul/cluster/ConsulClusterService.java       |  2 +
 .../services/org/apache/camel/cluster-service-file |  2 +
 .../file/cluster/FileLockClusterService.java       |  2 +
 .../cluster/InfinispanRemoteClusterService.java    |  2 +
 .../raft/cluster/JGroupsRaftClusterService.java    |  2 +
 .../cluster/KubernetesClusterService.java          |  2 +
 .../zookeeper/cluster/ZooKeeperClusterService.java |  2 +
 .../org/apache/camel/main/BaseMainSupport.java     | 61 +++++++++++++++++++++-
 8 files changed, 74 insertions(+), 1 deletion(-)

diff --git 
a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cluster/ConsulClusterService.java
 
b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cluster/ConsulClusterService.java
index ae40b63e23f0..8d754190ad90 100644
--- 
a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cluster/ConsulClusterService.java
+++ 
b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cluster/ConsulClusterService.java
@@ -21,10 +21,12 @@ import java.time.temporal.ChronoUnit;
 
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.cluster.AbstractCamelClusterService;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.util.ObjectHelper;
 
+@JdkService("cluster-service-consul")
 @Metadata(label = "bean",
           description = "A consul based cluster locking",
           annotations = { 
"interfaceName=org.apache.camel.cluster.CamelClusterService" })
diff --git 
a/components/camel-file/src/generated/resources/META-INF/services/org/apache/camel/cluster-service-file
 
b/components/camel-file/src/generated/resources/META-INF/services/org/apache/camel/cluster-service-file
new file mode 100644
index 000000000000..b7e0112b7785
--- /dev/null
+++ 
b/components/camel-file/src/generated/resources/META-INF/services/org/apache/camel/cluster-service-file
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.file.cluster.FileLockClusterService
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/cluster/FileLockClusterService.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/cluster/FileLockClusterService.java
index 40415e75ceed..f36f96b806be 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/cluster/FileLockClusterService.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/cluster/FileLockClusterService.java
@@ -24,9 +24,11 @@ import java.util.concurrent.locks.Lock;
 import org.apache.camel.CamelContext;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.cluster.AbstractCamelClusterService;
 import org.apache.camel.util.ObjectHelper;
 
+@JdkService("cluster-service-file")
 @Metadata(label = "bean",
           description = "A file based cluster locking (read documentation to 
understand limitations)",
           annotations = { 
"interfaceName=org.apache.camel.cluster.CamelClusterService" })
diff --git 
a/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/cluster/InfinispanRemoteClusterService.java
 
b/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/cluster/InfinispanRemoteClusterService.java
index fdd540d34dee..f3015c27606c 100644
--- 
a/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/cluster/InfinispanRemoteClusterService.java
+++ 
b/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/cluster/InfinispanRemoteClusterService.java
@@ -23,10 +23,12 @@ import 
org.apache.camel.component.infinispan.cluster.InfinispanClusterService;
 import org.apache.camel.component.infinispan.cluster.InfinispanClusterView;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.util.ObjectHelper;
 import org.infinispan.client.hotrod.RemoteCacheManager;
 import org.infinispan.client.hotrod.configuration.Configuration;
 
+@JdkService("cluster-service-infinispan")
 @Metadata(label = "bean",
           description = "Infinispan based remote cluster locking",
           annotations = { 
"interfaceName=org.apache.camel.cluster.CamelClusterService" })
diff --git 
a/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterService.java
 
b/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterService.java
index 503eb7a596df..8c481dabe678 100644
--- 
a/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterService.java
+++ 
b/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterService.java
@@ -18,9 +18,11 @@ package org.apache.camel.component.jgroups.raft.cluster;
 
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.cluster.AbstractCamelClusterService;
 import org.jgroups.raft.RaftHandle;
 
+@JdkService("cluster-service-jgroups-raft")
 @Metadata(label = "bean",
           description = "JGroups Raft based cluster locking",
           annotations = { 
"interfaceName=org.apache.camel.cluster.CamelClusterService" })
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/cluster/KubernetesClusterService.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/cluster/KubernetesClusterService.java
index c1973ebc6196..7b43d8178f07 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/cluster/KubernetesClusterService.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/cluster/KubernetesClusterService.java
@@ -28,12 +28,14 @@ import 
org.apache.camel.component.kubernetes.KubernetesConfiguration;
 import 
org.apache.camel.component.kubernetes.cluster.lock.KubernetesLockConfiguration;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.cluster.AbstractCamelClusterService;
 import org.apache.camel.util.ObjectHelper;
 
 /**
  * A Kubernetes based cluster service leveraging Kubernetes optimistic locks 
on resources (specifically ConfigMaps).
  */
+@JdkService("cluster-service-kubernetes")
 @Metadata(label = "bean",
           description = "Kubernetes based cluster locking",
           annotations = { 
"interfaceName=org.apache.camel.cluster.CamelClusterService" })
diff --git 
a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cluster/ZooKeeperClusterService.java
 
b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cluster/ZooKeeperClusterService.java
index 878e68eb493c..438b87b58bba 100644
--- 
a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cluster/ZooKeeperClusterService.java
+++ 
b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cluster/ZooKeeperClusterService.java
@@ -20,6 +20,7 @@ import 
org.apache.camel.component.zookeeper.ZooKeeperCuratorConfiguration;
 import org.apache.camel.component.zookeeper.ZooKeeperCuratorHelper;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.cluster.AbstractCamelClusterService;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.ObjectHelper;
@@ -28,6 +29,7 @@ import org.apache.curator.framework.CuratorFramework;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+@JdkService("cluster-service-zookeeper")
 @Metadata(label = "bean",
           description = "ZooKeeper based cluster locking",
           annotations = { 
"interfaceName=org.apache.camel.cluster.CamelClusterService" })
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java 
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index d0b745a39c09..b933427bf2c7 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -54,6 +54,7 @@ import org.apache.camel.PropertiesLookupListener;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.Service;
 import org.apache.camel.StartupStep;
+import org.apache.camel.cluster.CamelClusterService;
 import org.apache.camel.console.DevConsole;
 import org.apache.camel.console.DevConsoleRegistry;
 import org.apache.camel.health.HealthCheck;
@@ -153,7 +154,7 @@ public abstract class BaseMainSupport extends BaseService {
     private static final String[] GROUP_PREFIXES = new String[] {
             "camel.context.", "camel.resilience4j.", "camel.faulttolerance.",
             "camel.rest.", "camel.vault.", "camel.threadpool.", 
"camel.health.",
-            "camel.lra.", "camel.opentelemetry2.", "camel.opentelemetry.",
+            "camel.lra.", "camel.cluster.", "camel.opentelemetry2.", 
"camel.opentelemetry.",
             "camel.telemetryDev.", "camel.management.", "camel.mdc.", 
"camel.metrics.", "camel.routeTemplate",
             "camel.devConsole.", "camel.variable.", "camel.beans.", 
"camel.globalOptions.",
             PREFIX_SERVER, PREFIX_SSL, PREFIX_SECURITY, PREFIX_DEBUG, 
PREFIX_TRACE,
@@ -1396,6 +1397,7 @@ public abstract class BaseMainSupport extends BaseService 
{
         OrderedLocationProperties threadPoolProperties = new 
OrderedLocationProperties();
         OrderedLocationProperties healthProperties = new 
OrderedLocationProperties();
         OrderedLocationProperties lraProperties = new 
OrderedLocationProperties();
+        OrderedLocationProperties clusterProperties = new 
OrderedLocationProperties();
         OrderedLocationProperties otelProperties = new 
OrderedLocationProperties();
         OrderedLocationProperties otel2Properties = new 
OrderedLocationProperties();
         OrderedLocationProperties mdcProperties = new 
OrderedLocationProperties();
@@ -1459,6 +1461,12 @@ public abstract class BaseMainSupport extends 
BaseService {
                 String option = key.substring(10);
                 validateOptionAndValue(key, option, value);
                 lraProperties.put(loc, optionKey(option), value);
+            } else if (startsWithIgnoreCase(key, "camel.cluster.")) {
+                // grab the value
+                String value = prop.getProperty(key);
+                String option = key.substring(14);
+                validateOptionAndValue(key, option, value);
+                clusterProperties.put(loc, optionKey(option), value);
             } else if (startsWithIgnoreCase(key, "camel.opentelemetry2.")) {
                 // grab the value
                 String value = prop.getProperty(key);
@@ -1646,6 +1654,12 @@ public abstract class BaseMainSupport extends 
BaseService {
             setLraCheckProperties(camelContext, lraProperties, 
mainConfigurationProperties.isAutoConfigurationFailFast(),
                     autoConfiguredProperties);
         }
+        if (!clusterProperties.isEmpty()) {
+            LOG.debug("Auto-configuring Cluster Service from loaded 
properties: {}", clusterProperties.size());
+            setClusterServiceProperties(camelContext, clusterProperties,
+                    mainConfigurationProperties.isAutoConfigurationFailFast(),
+                    autoConfiguredProperties);
+        }
         if (!otelProperties.isEmpty() || 
mainConfigurationProperties.hasOtelConfiguration()) {
             LOG.debug("Auto-configuring OpenTelemetry from loaded properties: 
{}", otelProperties.size());
             setOtelProperties(camelContext, otelProperties, 
mainConfigurationProperties.isAutoConfigurationFailFast(),
@@ -1980,6 +1994,51 @@ public abstract class BaseMainSupport extends 
BaseService {
         }
     }
 
+    private void setClusterServiceProperties(
+            CamelContext camelContext, OrderedLocationProperties 
clusterProperties,
+            boolean failIfNotSet, OrderedLocationProperties 
autoConfiguredProperties)
+            throws Exception {
+
+        // group properties by cluster type (first dot-delimited segment)
+        // e.g., "file.root" -> type="file", option="root"
+        Map<String, OrderedLocationProperties> byType = new LinkedHashMap<>();
+        for (String key : clusterProperties.stringPropertyNames()) {
+            String loc = clusterProperties.getLocation(key);
+            String value = clusterProperties.getProperty(key);
+            int dot = key.indexOf('.');
+            if (dot > 0) {
+                String type = key.substring(0, dot);
+                String option = key.substring(dot + 1);
+                byType.computeIfAbsent(type, k -> new 
OrderedLocationProperties())
+                        .put(loc, optionKey(option), value);
+            }
+        }
+
+        for (Map.Entry<String, OrderedLocationProperties> entry : 
byType.entrySet()) {
+            String type = entry.getKey();
+            OrderedLocationProperties typeProperties = entry.getValue();
+
+            String enabledLoc = typeProperties.getLocation("enabled");
+            Object enabledObj = typeProperties.remove("enabled");
+            if (ObjectHelper.isNotEmpty(enabledObj)) {
+                autoConfiguredProperties.put(enabledLoc, "camel.cluster." + 
type + ".enabled", enabledObj.toString());
+            }
+            boolean enabled = enabledObj != null ? 
CamelContextHelper.parseBoolean(camelContext, enabledObj.toString()) : true;
+            if (enabled) {
+                CamelClusterService css = resolveClusterService(camelContext, 
type);
+                setPropertiesOnTarget(camelContext, css, typeProperties, 
"camel.cluster." + type + ".", failIfNotSet, true,
+                        autoConfiguredProperties);
+                camelContext.addService(css, true, true);
+            }
+        }
+    }
+
+    private static CamelClusterService resolveClusterService(CamelContext 
camelContext, String type) throws Exception {
+        String factoryKey = "cluster-service-" + type;
+        return ResolverHelper.resolveMandatoryBootstrapService(
+                camelContext, factoryKey, CamelClusterService.class, "camel-" 
+ type);
+    }
+
     private void setOtelProperties(
             CamelContext camelContext, OrderedLocationProperties 
otelProperties,
             boolean failIfNotSet, OrderedLocationProperties 
autoConfiguredProperties)

Reply via email to