This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1e42e3cefcd9 CAMEL-23680: Add group as scope to variables (#23813)
1e42e3cefcd9 is described below
commit 1e42e3cefcd9266b01385b2de39101497e928c7d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jun 8 09:50:20 2026 +0200
CAMEL-23680: Add group as scope to variables (#23813)
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude <[email protected]>
---
.../camel/spi/VariableRepositoryFactory.java | 7 +
.../engine/DefaultVariableRepositoryFactory.java | 20 ++
.../camel/processor/SetGroupVariableTest.java | 126 ++++++++++++
.../camel/support/GroupVariableRepositoryTest.java | 194 +++++++++++++++++++
.../camel/main/MainSupportModelConfigurer.java | 4 +
.../camel/support/GroupVariableRepository.java | 215 +++++++++++++++++++++
docs/user-manual/modules/ROOT/pages/variables.adoc | 38 +++-
7 files changed, 600 insertions(+), 4 deletions(-)
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java
b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java
index 749f56adcfd3..4a190295edc2 100644
---
a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java
+++
b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java
@@ -35,6 +35,13 @@ public interface VariableRepositoryFactory {
*/
String ROUTE_VARIABLE_REPOSITORY_ID = "route-variable-repository";
+ /**
+ * Registry bean id for group {@link VariableRepository}.
+ *
+ * @since 4.21
+ */
+ String GROUP_VARIABLE_REPOSITORY_ID = "group-variable-repository";
+
/**
* Gets the {@link VariableRepository} for the given id
*
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java
index 8d834c145edb..d130c8c282b5 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java
@@ -27,6 +27,7 @@ import org.apache.camel.spi.VariableRepository;
import org.apache.camel.spi.VariableRepositoryFactory;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.GlobalVariableRepository;
+import org.apache.camel.support.GroupVariableRepository;
import org.apache.camel.support.LifecycleStrategySupport;
import org.apache.camel.support.RouteVariableRepository;
import org.apache.camel.support.service.ServiceSupport;
@@ -45,6 +46,7 @@ public class DefaultVariableRepositoryFactory extends
ServiceSupport implements
private final CamelContext camelContext;
private VariableRepository global;
private VariableRepository route;
+ private VariableRepository group;
private FactoryFinder factoryFinder;
public DefaultVariableRepositoryFactory(CamelContext camelContext) {
@@ -64,6 +66,9 @@ public class DefaultVariableRepositoryFactory extends
ServiceSupport implements
if (route != null && "route".equals(id)) {
return route;
}
+ if (group != null && "group".equals(id)) {
+ return group;
+ }
VariableRepository repo = CamelContextHelper.lookup(camelContext, id,
VariableRepository.class);
if (repo == null) {
@@ -119,6 +124,18 @@ public class DefaultVariableRepositoryFactory extends
ServiceSupport implements
camelContext.getRegistry().bind(ROUTE_VARIABLE_REPOSITORY_ID,
route);
}
+ // let's see if there is a custom group repo
+ repo = getVariableRepository("group");
+ if (repo != null) {
+ if (!(repo instanceof GroupVariableRepository)) {
+ LOG.info("Using VariableRepository: {} as group repository",
repo.getId());
+ }
+ group = repo;
+ } else {
+ group = new GroupVariableRepository();
+ camelContext.getRegistry().bind(GROUP_VARIABLE_REPOSITORY_ID,
group);
+ }
+
if (!camelContext.hasService(global)) {
camelContext.addService(global);
}
@@ -134,6 +151,9 @@ public class DefaultVariableRepositoryFactory extends
ServiceSupport implements
}
});
}
+ if (!camelContext.hasService(group)) {
+ camelContext.addService(group);
+ }
}
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/SetGroupVariableTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/SetGroupVariableTest.java
new file mode 100644
index 000000000000..fe7ce711b973
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/SetGroupVariableTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.processor;
+
+import java.util.List;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class SetGroupVariableTest extends ContextTestSupport {
+
+ private MockEndpoint end;
+
+ @Test
+ public void testSetGroupVariable() throws Exception {
+ assertNull(context.getVariable("group:teamA:foo"));
+ assertNull(context.getVariable("group:teamB:foo"));
+
+ end.expectedMessageCount(2);
+
+ template.sendBody("direct:teamA", "<blah/>");
+ template.sendBody("direct:teamB", "<blah/>");
+
+ assertMockEndpointsSatisfied();
+
+ // variables should be stored on exchange, not accessible from
exchange directly
+ List<Exchange> exchanges = end.getExchanges();
+ Exchange exchange = exchanges.get(0);
+ assertNull(exchange.getVariable("foo"));
+
+ // should be stored as group variables
+ assertEquals("bar", context.getVariable("group:teamA:foo"));
+ assertEquals("baz", context.getVariable("group:teamB:foo"));
+ }
+
+ @Test
+ public void testGroupVariableIsolation() throws Exception {
+ end.expectedMessageCount(1);
+
+ template.sendBody("direct:teamA", "<blah/>");
+
+ assertMockEndpointsSatisfied();
+
+ // teamA has the variable, teamB does not
+ assertEquals("bar", context.getVariable("group:teamA:foo"));
+ assertNull(context.getVariable("group:teamB:foo"));
+ }
+
+ @Test
+ public void testGroupVariableSimpleLanguage() throws Exception {
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedBodiesReceived("Value is bar");
+
+ template.sendBody("direct:simple", "<blah/>");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testCrossRouteGroupVariable() throws Exception {
+ MockEndpoint mock = getMockEndpoint("mock:cross");
+ mock.expectedBodiesReceived("shared-value");
+
+ // route "setter" sets the group variable, route "reader" reads it
+ template.sendBody("direct:setter", "<blah/>");
+ template.sendBody("direct:reader", "<blah/>");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ @Override
+ @BeforeEach
+ public void setUp() throws Exception {
+ super.setUp();
+ end = getMockEndpoint("mock:end");
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ public void configure() {
+ from("direct:teamA").routeId("routeA")
+ .setVariable("group:teamA:foo").constant("bar")
+ .to("mock:end");
+
+ from("direct:teamB").routeId("routeB")
+ .setVariable("group:teamB:foo").constant("baz")
+ .to("mock:end");
+
+ from("direct:simple").routeId("routeSimple")
+ .setVariable("group:teamA:foo").constant("bar")
+ .transform().simple("Value is
${variable.group:teamA:foo}")
+ .to("mock:result");
+
+ from("direct:setter").routeId("routeSetter")
+
.setVariable("group:shared:myKey").constant("shared-value")
+ .to("mock:end");
+
+ from("direct:reader").routeId("routeReader")
+ .setBody().variable("group:shared:myKey")
+ .to("mock:cross");
+ }
+ };
+ }
+}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/support/GroupVariableRepositoryTest.java
b/core/camel-core/src/test/java/org/apache/camel/support/GroupVariableRepositoryTest.java
new file mode 100644
index 000000000000..435acaece322
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/support/GroupVariableRepositoryTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.support;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GroupVariableRepositoryTest {
+
+ private GroupVariableRepository repo;
+
+ @BeforeEach
+ public void setUp() {
+ repo = new GroupVariableRepository();
+ }
+
+ @Test
+ public void testGetId() {
+ assertEquals("group", repo.getId());
+ }
+
+ @Test
+ public void testSetAndGetVariable() {
+ repo.setVariable("teamA:foo", "bar");
+ assertEquals("bar", repo.getVariable("teamA:foo"));
+ }
+
+ @Test
+ public void testVariableIsolationBetweenGroups() {
+ repo.setVariable("teamA:key", "valueA");
+ repo.setVariable("teamB:key", "valueB");
+
+ assertEquals("valueA", repo.getVariable("teamA:key"));
+ assertEquals("valueB", repo.getVariable("teamB:key"));
+ }
+
+ @Test
+ public void testMultipleVariablesInSameGroup() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamA:bar", "2");
+ repo.setVariable("teamA:baz", "3");
+
+ assertEquals("1", repo.getVariable("teamA:foo"));
+ assertEquals("2", repo.getVariable("teamA:bar"));
+ assertEquals("3", repo.getVariable("teamA:baz"));
+ assertEquals(3, repo.size());
+ }
+
+ @Test
+ public void testGetNonExistentVariable() {
+ assertNull(repo.getVariable("teamA:missing"));
+ }
+
+ @Test
+ public void testGetNonExistentGroup() {
+ assertNull(repo.getVariable("noSuchGroup:key"));
+ }
+
+ @Test
+ public void testRemoveVariable() {
+ repo.setVariable("teamA:foo", "bar");
+ Object removed = repo.removeVariable("teamA:foo");
+
+ assertEquals("bar", removed);
+ assertNull(repo.getVariable("teamA:foo"));
+ }
+
+ @Test
+ public void testRemoveWildcard() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamA:bar", "2");
+ repo.setVariable("teamB:baz", "3");
+
+ repo.removeVariable("teamA:*");
+
+ assertNull(repo.getVariable("teamA:foo"));
+ assertNull(repo.getVariable("teamA:bar"));
+ assertEquals("3", repo.getVariable("teamB:baz"));
+ assertEquals(1, repo.size());
+ }
+
+ @Test
+ public void testSetNullRemoves() {
+ repo.setVariable("teamA:foo", "bar");
+ repo.setVariable("teamA:foo", null);
+
+ assertNull(repo.getVariable("teamA:foo"));
+ }
+
+ @Test
+ public void testGetGroupIds() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamB:bar", "2");
+ repo.setVariable("teamC:baz", "3");
+
+ Set<String> ids = repo.getGroupIds();
+ assertEquals(Set.of("teamA", "teamB", "teamC"), ids);
+ }
+
+ @Test
+ public void testGetGroupIdsEmpty() {
+ assertTrue(repo.getGroupIds().isEmpty());
+ }
+
+ @Test
+ public void testGetGroupIdsAfterRemoveWildcard() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamB:bar", "2");
+
+ repo.removeVariable("teamA:*");
+
+ assertEquals(Set.of("teamB"), repo.getGroupIds());
+ }
+
+ @Test
+ public void testNames() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamB:bar", "2");
+
+ Set<String> names = repo.names().collect(Collectors.toSet());
+ assertEquals(Set.of("teamA:foo", "teamB:bar"), names);
+ }
+
+ @Test
+ public void testGetVariables() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamB:bar", "2");
+
+ Map<String, Object> vars = repo.getVariables();
+ assertEquals(2, vars.size());
+ assertEquals("1", vars.get("teamA:foo"));
+ assertEquals("2", vars.get("teamB:bar"));
+ }
+
+ @Test
+ public void testHasVariables() {
+ assertFalse(repo.hasVariables());
+
+ repo.setVariable("teamA:foo", "bar");
+ assertTrue(repo.hasVariables());
+ }
+
+ @Test
+ public void testSize() {
+ assertEquals(0, repo.size());
+
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamA:bar", "2");
+ repo.setVariable("teamB:baz", "3");
+ assertEquals(3, repo.size());
+ }
+
+ @Test
+ public void testClear() {
+ repo.setVariable("teamA:foo", "1");
+ repo.setVariable("teamB:bar", "2");
+
+ repo.clear();
+
+ assertFalse(repo.hasVariables());
+ assertEquals(0, repo.size());
+ }
+
+ @Test
+ public void testMissingColonThrows() {
+ assertThrows(IllegalArgumentException.class, () ->
repo.getVariable("noColon"));
+ assertThrows(IllegalArgumentException.class, () ->
repo.setVariable("noColon", "value"));
+ assertThrows(IllegalArgumentException.class, () ->
repo.removeVariable("noColon"));
+ }
+}
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/MainSupportModelConfigurer.java
b/core/camel-main/src/main/java/org/apache/camel/main/MainSupportModelConfigurer.java
index 117d75e87b6d..935f7347617a 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/MainSupportModelConfigurer.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/MainSupportModelConfigurer.java
@@ -101,6 +101,10 @@ public final class MainSupportModelConfigurer {
id = "route";
key = key.substring(6);
key = StringHelper.replaceFirst(key, ".", ":");
+ } else if (key.startsWith("group.")) {
+ id = "group";
+ key = key.substring(6);
+ key = StringHelper.replaceFirst(key, ".", ":");
} else if (key.startsWith("global.")) {
id = "global";
key = key.substring(7);
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/GroupVariableRepository.java
b/core/camel-support/src/main/java/org/apache/camel/support/GroupVariableRepository.java
new file mode 100644
index 000000000000..a9f9e33d94ee
--- /dev/null
+++
b/core/camel-support/src/main/java/org/apache/camel/support/GroupVariableRepository.java
@@ -0,0 +1,215 @@
+/*
+ * 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.support;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.StreamCache;
+import org.apache.camel.StreamCacheException;
+import org.apache.camel.spi.BrowsableVariableRepository;
+import org.apache.camel.spi.StreamCachingStrategy;
+import org.apache.camel.spi.VariableRepository;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * Group {@link VariableRepository} which stores variables in-memory per named
group.
+ * <p>
+ * Variables are scoped by group name using the syntax {@code
groupId:variableName}. This allows sharing variables
+ * across a subset of routes — wider than per-route but narrower than global
scope.
+ *
+ * @since 4.21
+ */
+public final class GroupVariableRepository extends ServiceSupport implements
BrowsableVariableRepository, CamelContextAware {
+
+ private final Map<String, Map<String, Object>> groups = new
ConcurrentHashMap<>();
+ private CamelContext camelContext;
+ private StreamCachingStrategy strategy;
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public Object getVariable(String name) {
+ String id = StringHelper.before(name, ":");
+ String key = StringHelper.after(name, ":");
+ if (id == null || key == null) {
+ throw new IllegalArgumentException("Name must be groupId:name
syntax");
+ }
+ Object answer = null;
+ Map<String, Object> variables = groups.get(id);
+ if (variables != null) {
+ answer = variables.get(key);
+ }
+ if (answer instanceof StreamCache sc) {
+ // reset so the cache is ready to be used as a variable
+ sc.reset();
+ }
+ return answer;
+ }
+
+ @Override
+ public void setVariable(String name, Object value) {
+ String id = StringHelper.before(name, ":");
+ String key = StringHelper.after(name, ":");
+ if (id == null || key == null) {
+ throw new IllegalArgumentException("Name must be groupId:name
syntax");
+ }
+
+ if (value != null && strategy != null) {
+ StreamCache sc = convertToStreamCache(value);
+ if (sc != null) {
+ value = sc;
+ }
+ }
+ if (value != null) {
+ Map<String, Object> variables = groups.computeIfAbsent(id, s ->
new ConcurrentHashMap<>(8));
+ // avoid the NullPointException
+ variables.put(key, value);
+ } else {
+ // if the value is null, we just remove the key from the map
+ Map<String, Object> variables = groups.get(id);
+ if (variables != null) {
+ variables.remove(key);
+ }
+ }
+ }
+
+ public boolean hasVariables() {
+ for (var vars : groups.values()) {
+ if (!vars.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int size() {
+ int size = 0;
+ for (var vars : groups.values()) {
+ size += vars.size();
+ }
+ return size;
+ }
+
+ public Stream<String> names() {
+ List<String> answer = new ArrayList<>();
+ for (Map.Entry<String, Map<String, Object>> entry : groups.entrySet())
{
+ String id = entry.getKey();
+ Map<String, Object> values = entry.getValue();
+ for (var e : values.entrySet()) {
+ answer.add(id + ":" + e.getKey());
+ }
+ }
+ return answer.stream();
+ }
+
+ public Map<String, Object> getVariables() {
+ Map<String, Object> answer = new ConcurrentHashMap<>();
+ for (Map.Entry<String, Map<String, Object>> entry : groups.entrySet())
{
+ String id = entry.getKey();
+ Map<String, Object> values = entry.getValue();
+ for (var e : values.entrySet()) {
+ answer.put(id + ":" + e.getKey(), e.getValue());
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Gets the ids of all groups that currently have variables.
+ */
+ public Set<String> getGroupIds() {
+ return Collections.unmodifiableSet(groups.keySet());
+ }
+
+ public void clear() {
+ groups.clear();
+ }
+
+ @Override
+ public String getId() {
+ return "group";
+ }
+
+ @Override
+ public Object removeVariable(String name) {
+ String id = StringHelper.before(name, ":");
+ String key = StringHelper.after(name, ":");
+ if (id == null || key == null) {
+ throw new IllegalArgumentException("Name must be groupId:name
syntax");
+ }
+
+ Map<String, Object> variables = groups.get(id);
+ if (variables != null) {
+ if ("*".equals(key)) {
+ variables.clear();
+ groups.remove(id);
+ return null;
+ } else {
+ return variables.remove(key);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ super.doInit();
+
+ if (camelContext != null && camelContext.isStreamCaching()) {
+ strategy = camelContext.getStreamCachingStrategy();
+ }
+ }
+
+ private StreamCache convertToStreamCache(Object body) {
+ // check if body is already cached
+ if (body == null) {
+ return null;
+ } else if (body instanceof StreamCache sc) {
+ // reset so the cache is ready to be used before processing
+ sc.reset();
+ return sc;
+ }
+ return tryStreamCache(body);
+ }
+
+ private StreamCache tryStreamCache(Object body) {
+ try {
+ // cache the body and if we could do that replace it as the new
body
+ return strategy.cache(body);
+ } catch (Exception e) {
+ throw new StreamCacheException(body, e);
+ }
+ }
+
+}
diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc
b/docs/user-manual/modules/ROOT/pages/variables.adoc
index 239019c0e855..8130a485d5ff 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -5,7 +5,7 @@
In Camel 4.4, we have introduced the concept of _variables_.
A variable is a key/value that can hold a value that can either be private per
`Exchange`,
-or shared per route, or per `CamelContext`.
+or shared per route, per named group, or per `CamelContext`.
IMPORTANT: You can also use _exchange properties_ as variables, but the
exchange properties are also used internally by Camel,
and some EIPs and components. With the newly introduced _variables_ then these
are exclusively for end users.
@@ -16,6 +16,7 @@ The variables are stored in one or more
`org.apache.camel.spi.VariableRepository
- `ExchangeVariableRepository` - A private repository per `Exchange` that
holds variables that are private for the lifecycle of each `Exchange`.
- `RouteVariableRepository` - Uses `route` as id. A single repository, that
holds variables per `Route`.
+- `GroupVariableRepository` - Uses `group` as id. A single repository, that
holds variables per named group. This allows sharing variables across a subset
of routes — wider than per-route but narrower than global scope.
- `GlobalVariableRepository` - Uses `global` as id. A single global repository
for the entire `CamelContext`.
The `ExchangeVariableRepository` is special as its private per exchange and is
the default repository when used during routing.
@@ -30,9 +31,9 @@ from management tooling, CLI, and the developer console.
You can implement custom `org.apache.camel.spi.VariableRepository` and plugin
to be used out of the box with Apache Camel.
For example, you can build a custom repository that stores the variables in a
database, so they are persisted.
-Each repository must have its own unique id. However, it's also possible to
replace the default `global`, or `route` repositories with another.
+Each repository must have its own unique id. However, it's also possible to
replace the default `global`, `route`, or `group` repositories with another.
-IMPORTANT: The id `exchange` and `header` is reserved by Camel internally and
should not be used as id for custom repositories.
+IMPORTANT: The id `exchange`, `header`, `global`, `route`, and `group` are
reserved by Camel internally and should not be used as id for custom
repositories.
== Getting and setting variables from Java API
@@ -93,6 +94,26 @@ Object val =
context.getVariable("route:myRouteId:myRouteKey");
String str = context.getVariable("route:myRouteId:myRouteKey", String.class);
----
+You can assign a variable to a named group with `group:` as follows:
+
+[source,java]
+----
+exchange.setVariable("group:teamA:myKey", someObjectHere);
+----
+
+And you can get group variables as well:
+
+[source,java]
+----
+Object val = exchange.getVariable("group:teamA:myKey");
+
+// you can get the value as a specific type
+String str = exchange.getVariable("group:teamA:myKey", String.class);
+----
+
+Group variables are shared across all routes that use the same group name,
+which makes them useful for sharing state between a subset of related routes.
+
== Setting and getting variables from DSL
It is also possible to use variables in Camel xref:routes.adoc[routes] using
the:
@@ -233,7 +254,7 @@ YAML::
== Configuring initial variables on startup
-When Camel is starting then it's possible to configure initial variables for
`global` and `route` repositories only.
+When Camel is starting then it's possible to configure initial variables for
`global`, `route`, and `group` repositories only.
This can be done in `application.properties` as shown below:
@@ -256,6 +277,15 @@ camel.variable.random = 999
Here the gold variable is set on the `route` repository, and the other
variables are set on the `global` repository.
+You can also set group scoped variables, using `group.` as prefix. The dot is
used to separate
+the group id from the variable name, eg `teamA.threshold`.
+
+[source,properties]
+----
+camel.variable.group.teamA.threshold = 100
+camel.variable.group.teamB.region = EU
+----
+
The value of a variable can also be loaded from the file system, such as a
JSon file. To do this, you should
prefix the value with `resource:file:` or `resource:classpath:` to load from
the file system or classpath,
as shown below: