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)
