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

Reply via email to