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 72701dd57007 CAMEL-25678: camel-core - Switch case values and Dynamic
Router uriDelimiter resolve property placeholders
72701dd57007 is described below
commit 72701dd5700788539872ed1406eef406d0331e43
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 16 20:29:33 2026 +0530
CAMEL-25678: camel-core - Switch case values and Dynamic Router
uriDelimiter resolve property placeholders
[CAMEL-25678](https://issues.apache.org/jira/browse/CAMEL-25678)
Every String option of an EIP is resolved with `parseString` when the route
is created, which is also how route template and Kamelet parameters reach an
EIP (CAMEL-25047 resolved several more of them). Two options were used as
written:
- **Switch EIP, case `value`.** `SwitchReifier` resolved the `uri` of each
case and of `otherwise` (`staticUri` calls `parseString`), but built the table
from `c.getValue().toLowerCase(Locale.ENGLISH)`. A case such as
`doCase("{{tier}}", "direct:gold")` was keyed by the text `{{tier}}`, so no
selector result matched it: every message went to `otherwise`, or continued
after the Switch without a destination when there is no `otherwise`, and
nothing was logged. This includes a route temp [...]
- **Dynamic Router EIP, `uriDelimiter`.** `DynamicRouterReifier` used the
delimiter as written, while `RoutingSlipReifier` and `RecipientListReifier`
resolve theirs. With `uriDelimiter("{{delim}}")` the slip was not split and the
exchange was sent to one endpoint whose uri is the whole slip.
Closes #27917
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../org/apache/camel/catalog/docs/switch-eip.adoc | 7 +--
.../main/docs/modules/eips/pages/switch-eip.adoc | 7 +--
.../apache/camel/reifier/DynamicRouterReifier.java | 6 ++-
.../org/apache/camel/reifier/SwitchReifier.java | 6 ++-
.../DynamicRouterUriDelimiterPlaceholderTest.java | 57 ++++++++++++++++++++++
.../apache/camel/processor/SwitchEndpointTest.java | 29 +++++++++++
.../camel/processor/SwitchValidationTest.java | 9 ++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 7 +++
8 files changed, 118 insertions(+), 10 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc
index 4a58398e1468..ce0d869da744 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc
@@ -198,9 +198,10 @@ handling routes shown above.
== Destinations and management
-Case and fallback URIs support property placeholders resolved at startup.
Simple
-expressions in URIs are rejected. In YAML, both cases and `otherwise` accept
`uri`
-plus `parameters`:
+Case values and the case and fallback URIs support property placeholders
resolved
+at startup, so a route template or Kamelet parameter can be used as a case
value. A
+value is lower-cased after it is resolved. Simple expressions in URIs are
rejected.
+In YAML, both cases and `otherwise` accept `uri` plus `parameters`:
[source,yaml]
----
diff --git
a/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc
b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc
index 4a58398e1468..ce0d869da744 100644
--- a/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc
+++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc
@@ -198,9 +198,10 @@ handling routes shown above.
== Destinations and management
-Case and fallback URIs support property placeholders resolved at startup.
Simple
-expressions in URIs are rejected. In YAML, both cases and `otherwise` accept
`uri`
-plus `parameters`:
+Case values and the case and fallback URIs support property placeholders
resolved
+at startup, so a route template or Kamelet parameter can be used as a case
value. A
+value is lower-cased after it is resolved. Simple expressions in URIs are
rejected.
+In YAML, both cases and `otherwise` accept `uri` plus `parameters`:
[source,yaml]
----
diff --git
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
index 4ea8e67b0700..c9c5c13a42f3 100644
---
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
+++
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
@@ -33,8 +33,10 @@ public class DynamicRouterReifier extends
ExpressionReifier<DynamicRouterDefinit
@Override
public Processor createProcessor() throws Exception {
Expression expression = createExpression(definition.getExpression());
- String delimiter = definition.getUriDelimiter() != null
- ? definition.getUriDelimiter() :
DynamicRouterDefinition.DEFAULT_DELIMITER;
+ String delimiter = parseString(definition.getUriDelimiter());
+ if (delimiter == null) {
+ delimiter = DynamicRouterDefinition.DEFAULT_DELIMITER;
+ }
DynamicRouter dynamicRouter = new DynamicRouter(camelContext,
expression, delimiter);
dynamicRouter.setDisabled(isDisabled(camelContext, definition));
diff --git
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java
index 44ccda5e85e7..e4bbbf5c2585 100644
---
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java
+++
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java
@@ -48,10 +48,12 @@ public class SwitchReifier extends
ProcessorReifier<SwitchDefinition> {
List<String> caseKeys = new ArrayList<>();
List<String> uris = new ArrayList<>();
for (SwitchCaseDefinition c : definition.getCases()) {
- if (c.getValue() == null) {
+ // resolve property placeholders (also route template parameters),
as is already done for the uri
+ String value = parseString(c.getValue());
+ if (value == null) {
throw new IllegalArgumentException("Switch cases require
value");
}
- String key = c.getValue().toLowerCase(Locale.ENGLISH);
+ String key = value.toLowerCase(Locale.ENGLISH);
caseKeys.add(key);
uris.add(staticUri(c.getUri()));
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/DynamicRouterUriDelimiterPlaceholderTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/DynamicRouterUriDelimiterPlaceholderTest.java
new file mode 100644
index 000000000000..974465e74195
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/DynamicRouterUriDelimiterPlaceholderTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.Properties;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangeProperty;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+class DynamicRouterUriDelimiterPlaceholderTest extends ContextTestSupport {
+
+ @Test
+ void uriDelimiterResolvesPropertyPlaceholder() throws Exception {
+ getMockEndpoint("mock:a").expectedBodiesReceived("Hello World");
+ getMockEndpoint("mock:b").expectedBodiesReceived("Hello World");
+ template.sendBody("direct:start", "Hello World");
+ assertMockEndpointsSatisfied();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ Properties properties = new Properties();
+ properties.setProperty("slip.delimiter", ";");
+ context.getPropertiesComponent().setInitialProperties(properties);
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:start").dynamicRouter(method(DynamicRouterUriDelimiterPlaceholderTest.class,
"slip"))
+ .uriDelimiter("{{slip.delimiter}}");
+ }
+ };
+ }
+
+ /**
+ * The slip of the dynamic router: two endpoints separated by the
configured delimiter, then the end.
+ */
+ public static String slip(@ExchangeProperty(Exchange.SLIP_ENDPOINT) String
previous) {
+ return previous == null ? "mock:a;mock:b" : null;
+ }
+}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java
index eaf5952a4da7..c771c0c22b98 100644
---
a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java
@@ -23,6 +23,7 @@ import org.apache.camel.CamelContext;
import org.apache.camel.ContextTestSupport;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.TemplatedRouteBuilder;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.RouteDefinitionHelper;
import org.apache.camel.model.language.HeaderExpression;
@@ -73,15 +74,43 @@ class SwitchEndpointTest extends ContextTestSupport {
assertEquals(2, attempts.get());
}
+ @Test
+ void caseValueResolvesPropertyPlaceholder() throws Exception {
+ getMockEndpoint("mock:gold").expectedBodiesReceived("first", "second");
+ getMockEndpoint("mock:other").expectedBodiesReceived("third");
+ template.sendBodyAndHeader("direct:placeholderValue", "first", "tier",
"gold");
+ template.sendBodyAndHeader("direct:placeholderValue", "second",
"tier", "GOLD");
+ // the unresolved placeholder text is not a case value
+ template.sendBodyAndHeader("direct:placeholderValue", "third", "tier",
"{{switch.tier}}");
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ void caseValueResolvesRouteTemplateParameter() throws Exception {
+ TemplatedRouteBuilder.builder(context,
"tierSwitch").routeId("templated")
+ .parameter("in", "direct:templated").parameter("tier",
"silver").add();
+ getMockEndpoint("mock:gold").expectedBodiesReceived("first");
+ getMockEndpoint("mock:other").expectedBodiesReceived("second");
+ template.sendBodyAndHeader("direct:templated", "first", "tier",
"silver");
+ template.sendBodyAndHeader("direct:templated", "second", "tier",
"{{tier}}");
+ assertMockEndpointsSatisfied();
+ }
+
@Override
protected RouteBuilder createRouteBuilder() {
Properties properties = new Properties();
properties.setProperty("switch.destination", "mock:destination");
+ properties.setProperty("switch.tier", "gold");
context.getPropertiesComponent().setInitialProperties(properties);
return new RouteBuilder() {
@Override
public void configure() {
interceptSendToEndpoint("mock:destination").to("mock:intercepted");
+ from("direct:placeholderValue").doSwitch(header("tier"))
+ .doCase("{{switch.tier}}",
"mock:gold").otherwise("mock:other");
+
routeTemplate("tierSwitch").templateParameter("in").templateParameter("tier")
+ .from("{{in}}").doSwitch(header("tier"))
+ .doCase("{{tier}}",
"mock:gold").otherwise("mock:other");
from("direct:intercepted").routeId("intercepted").doSwitch(constant("match"))
.doCase("match",
"{{switch.destination}}").otherwise("mock:fallback");
from("direct:redelivery")
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java
index 962d45f43973..0f22d09c5244 100644
---
a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.processor;
+import java.util.Properties;
import java.util.function.Consumer;
import org.apache.camel.builder.RouteBuilder;
@@ -41,6 +42,11 @@ class SwitchValidationTest {
rejects(s -> s.doCase("Billing", "mock:a").doCase("billing",
"mock:b"), "Duplicate switch case");
}
+ @Test
+ void rejectsDuplicateValuesAfterResolvingPlaceholders() {
+ rejects(s -> s.doCase("{{switch.tier}}", "mock:a").doCase("gold",
"mock:b"), "Duplicate switch case");
+ }
+
@Test
void rejectsMissingUri() {
rejects(s -> s.doCase("billing", (String) null), "nonblank uri");
@@ -79,6 +85,9 @@ class SwitchValidationTest {
private void rejects(Consumer<SwitchDefinition> configure, String message)
{
Exception failure = assertThrows(Exception.class, () -> {
try (DefaultCamelContext context = new DefaultCamelContext()) {
+ Properties properties = new Properties();
+ properties.setProperty("switch.tier", "gold");
+
context.getPropertiesComponent().setInitialProperties(properties);
context.addRoutes(new RouteBuilder() {
@Override
public void configure() {
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 8bcccd225fc9..739fad1e7cb8 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -723,6 +723,13 @@ the old single header can set the header to a String, for
example with `String.v
This also applies to camel-graphql, which sends its requests with camel-http.
+=== camel-core - Switch case values and the Dynamic Router uriDelimiter
resolve property placeholders
+
+The `value` of a Switch case and the `uriDelimiter` of the Dynamic Router EIP
were used as written, so a property
+placeholder or a route template parameter in them never matched (Switch) or
did not split the slip (Dynamic Router).
+They are now resolved at startup like the other EIP options; a placeholder of
a property that does not exist now
+fails the route at startup.
+
=== camel-dapr - the saveBulk and executeTransaction state operations are sent
The `saveBulk` and `executeTransaction` operations of `dapr:state` built their
request but never sent it to Dapr, so