Federico Mariani created CAMEL-24187:
----------------------------------------
Summary: camel-core: CamelContext.getEndpoint() never caches a uri
containing a % character (double-normalization in
AbstractCamelContext.getEndpointKey)
Key: CAMEL-24187
URL: https://issues.apache.org/jira/browse/CAMEL-24187
Project: Camel
Issue Type: Bug
Reporter: Federico Mariani
Assignee: Federico Mariani
h3. Summary
{{AbstractCamelContext.doGetEndpoint()}} normalizes the incoming uri once for
its cache lookup, but on a cache miss it hands that *already normalized* uri to
{{addEndpointToRegistry()}}, which re-normalizes it a *second* time before
storing the registry key. {{EndpointHelper.normalizeEndpointUri()}} (via
{{URISupport.normalizeUri}} / {{UnsafeUriCharactersEncoder}}) is not idempotent
for any uri containing a literal {{%}} character - it encodes it to {{%25}}
every time it runs, so a second pass turns {{%41}} into {{%2541}}.
Because the stored key (double-normalized) never equals the lookup key computed
fresh on a later call (single-normalized), {{CamelContext#getEndpoint(String)}}
returns a brand-new {{Endpoint}} instance - and starts it - on *every single
call* for any uri containing a {{%}}, instead of reusing the cached singleton.
This applies to any endpoint uri with a literal {{%}} anywhere in it (path or
query), regardless of component.
h3. Reproduction
{code:java}
DefaultCamelContext ctx = new DefaultCamelContext();
ctx.start();
String uri = "controlbus:route?routeId=RAW(%41)&action=status";
Endpoint first = ctx.getEndpoint(uri);
Endpoint second = ctx.getEndpoint(uri);
// FAILS: first != second, even though both getEndpointUri() print the
identical text
assertSame(first, second);
{code}
A minimal, deterministic reproducer is attached as a patch adding
{{DefaultEndpointRegistryTest#testGetEndpointIsCachedForUriContainingPercentCharacter}}
in camel-core.
h3. Root cause (exact call chain)
* {{AbstractCamelContext.doGetEndpoint()}} normalizes the uri once (line ~810:
{{uri = EndpointHelper.normalizeEndpointUri(uri)}}), then builds the *lookup*
key as {{NormalizedUri.newNormalizedUri(uri, true)}} - {{true}} meaning
"already normalized, do not touch it again".
* On a cache miss it calls {{addEndpointToRegistry(uri, answer)}} with that
same already-normalized {{uri}}.
* {{addEndpointToRegistry()}} calls {{getEndpointKey(uri, endpoint)}}, which
calls {{NormalizedUri.newNormalizedUri(uri, false)}} - {{false}} meaning "not
normalized yet, please normalize it" - re-running
{{EndpointHelper.normalizeEndpointUri()}} on the already-normalized string.
Since normalization is not idempotent for {{%}}, the *stored* key and the
*lookup* key computed on the next call diverge permanently.
h3. Real-world impact - discovered via CAMEL-24171
This was discovered while investigating CAMEL-24171 (camel-plc4x:
{{Plc4XEndpoint#doStart()}} runs on every poll cycle for a S7 PLC route but not
for a structurally identical Modbus route). S7 tag addresses conventionally
contain a literal {{%}} (e.g. {{RAW(%DB1.DBX0.0:BOOL)}}), while Modbus
addresses typically do not (e.g. {{RAW(coil:1)}}). A route using {{pollEnrich}}
against a plc4x endpoint therefore resolves a *new* {{Plc4XEndpoint}} -
reloading the whole {{DefaultPlcDriverManager}} - on every single poll for S7,
while Modbus correctly reuses one cached endpoint for the life of the route.
camel-plc4x itself is not at fault; the same symptom would occur for any
component whose endpoint uri legitimately contains a {{%}} and is re-resolved
by string on every use (as {{pollEnrich}}/{{toD}}/{{recipientList}} do).
h3. Fix
Add a {{normalized}}-aware overload of
{{getEndpointKey}}/{{addEndpointToRegistry}} so only the {{doGetEndpoint}} call
site (whose uri is provably already normalized) skips the redundant second
normalization pass. The public {{hasEndpoint(String)}}, {{addEndpoint(String,
Endpoint)}} and {{removeEndpoints(String)}} APIs still pass raw,
caller-supplied uris and must keep normalizing exactly as before - a blanket
flip of the existing method would silently break those.
Verified: full {{camel-core}} test suite passes with the fix; the two isolating
reproducer tests ({{DefaultEndpointRegistryTest}} in camel-core,
{{Plc4XPollEnrichDoStartReproducerTest}} in camel-plc4x) both go from red to
green.
See also: CAMEL-24171 (the plc4x symptom this was found through).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)