shashank created CAMEL-25678:
--------------------------------
Summary: camel-core - Switch EIP case values and the Dynamic
Router uriDelimiter do not resolve property placeholders, so a case value with
a placeholder never matches and the slip is not split
Key: CAMEL-25678
URL: https://issues.apache.org/jira/browse/CAMEL-25678
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
Assignee: shashank
Every String option of an EIP is passed through property-placeholder resolution
when the route is created ({{AbstractReifier.parseString}}); this is also how
route template and Kamelet parameters reach an EIP. Two options are used as
written instead.
h3. 1. Switch EIP: the case value
{{SwitchReifier.createProcessor}} (main cd2ce899764e, lines 51-56) builds the
dispatch table from {{c.getValue().toLowerCase(Locale.ENGLISH)}}. The {{uri}}
of the case and of {{otherwise}} goes through {{staticUri}}, which calls
{{parseString}}, but the value does not. A case such as
{code:java}
from("direct:tickets")
.doSwitch(header("tier"))
.doCase("{{gold.code}}", "direct:gold")
.otherwise("direct:standard");
{code}
is keyed by the text \{\{gold.code\}\}. No selector result matches it (only a
header that literally contains the text \{\{gold.code\}\} does), so every
message goes to {{otherwise}}, or continues after the Switch without a
destination when there is no {{otherwise}}. Nothing is logged. The same happens
in XML and YAML, and for a route template or Kamelet parameter used as a case
value (doCase("\{\{tier\}\}", ...) in a {{routeTemplate}}), where a placeholder
is the only way to pass the parameter. The duplicate check also compares the
unresolved text, so two cases that resolve to the same value are not rejected
at startup as the documentation says.
The Switch documentation says that case and fallback URIs support property
placeholders and that values are "literal"; in the design (CAMEL-24988)
"literal" is used in contrast to patterns and wildcards. It does not say that
values do not support placeholders.
h3. 2. Dynamic Router EIP: uriDelimiter
{{DynamicRouterReifier.createProcessor}} (lines 36-37) uses
{{definition.getUriDelimiter()}} as written. {{RoutingSlipReifier}} (line 38)
and {{RecipientListReifier}} (line 56) resolve their delimiter with
{{parseString}}. With uriDelimiter("\{\{delim\}\}") and a slip
{{"mock:a;mock:b"}}, the slip is not split and the exchange is sent to one
endpoint whose uri is the whole slip (for {{mock:}}, a mock named
{{a;mock:b}}); with other components it fails or sends to an unexpected
endpoint.
h3. Reproduction
New tests in camel-core; on main:
* {{SwitchEndpointTest.caseValueResolvesPropertyPlaceholder}}:
doCase("\{\{switch.tier\}\}", "mock:gold") with {{switch.tier=gold}}, headers
{{gold}}, {{GOLD}} and the literal text \{\{switch.tier\}\}: {{mock://gold Body
of message: 0. Expected: <first> but was: <third>}} (only the literal
placeholder text matched).
* {{SwitchEndpointTest.caseValueResolvesRouteTemplateParameter}}: a route
template with doCase("\{\{tier\}\}", "mock:gold") and {{tier=silver}}:
{{mock://gold Body of message: 0. Expected: <first> but was: <second>}}.
* {{SwitchValidationTest.rejectsDuplicateValuesAfterResolvingPlaceholders}}:
doCase("\{\{switch.tier\}\}", ...) and {{doCase("gold", ...)}} start without
error: {{Expected java.lang.Exception to be thrown, but nothing was thrown.}}
* {{DynamicRouterUriDelimiterPlaceholderTest}}: a slip {{mock:a;mock:b}} with
uriDelimiter("\{\{slip.delimiter\}\}") and {{slip.delimiter=;}}: {{mock://a
Received message count. Expected: <1> but was: <0>}}.
h3. Proposed fix
Resolve both with {{parseString}} in the reifier: the case value before it is
lower-cased (and before the duplicate check), the {{uriDelimiter}} before the
default is applied. Values without placeholders are unchanged. A placeholder of
a property that does not exist then fails the route at startup, as it does for
the other options.
Not changed, same pattern, smaller impact (follow-ups if wanted):
{{RollbackReifier}} uses {{message}} as written (only the text of the
exception), and {{TransformDataTypeReifier}} uses {{fromType}}/{{toType}} as
written (fails at startup with "No transformer").
How it was found: a Lean 4 model of the Switch dispatch table (case value,
placeholder resolution, case folding) against the Switch documentation: on main
no selector result can select a case whose value contains a placeholder, and
with the value resolved before folding the table matches the documentation for
every value; the counterexample is checked by {{decide}}. A follow-up sweep of
all reifiers for String options that are not passed to {{parseString}} found
the Dynamic Router delimiter. The model is a sketch; the tests are the evidence.
Affected: main (cd2ce899764e; tested). Switch: camel-4.23.x (same line 54,
read, not run); the Switch EIP is not on camel-4.22.x and older. Dynamic
Router: camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x have the same
lines 36-37 (read, not run).
Duplicate check (2026-10-15): JIRA text "uriDelimiter" (only CAMEL-25083,
iterating a delimiter-only string), "switch case value placeholder",
"dynamicRouter placeholder", summaries with "placeholder" since June 2026
(CAMEL-24414, CAMEL-24282, CAMEL-24696 and others, none about these options);
GitHub pull requests "Switch EIP", "uriDelimiter", "SwitchReifier" (nothing
open or related).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)