[
https://issues.apache.org/jira/browse/CAMEL-25092?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25092:
--------------------------------
Fix Version/s: 4.23.0
> Property binding: nested list elements are appended, not created at their
> index, so a list of 11 or more beans or a list with gaps silently loses
> elements; the documented list key last fails
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25092
> URL: https://issues.apache.org/jira/browse/CAMEL-25092
> Project: Camel
> Issue Type: Bug
> Components: camel-core
> Reporter: shashank
> Priority: Minor
> Fix For: 4.23.0
>
>
> A normal, 0-based list of 11 or more nested beans configured with property
> binding silently loses elements. For example, a Camel Main bean with a list
> of servers:
> {noformat}
> camel.beans.cluster = #class:com.foo.Cluster
> camel.beans.cluster.servers[0].host = h0
> camel.beans.cluster.servers[0].port = 1000
> ...
> camel.beans.cluster.servers[10].host = h10
> camel.beans.cluster.servers[10].port = 1010
> {noformat}
> gives a list of 10 servers, {{h0:1000}} to {{h9:1009}}. The server
> {{h10:1010}} is gone, and there is no error or warning. With 12 servers,
> {{h10}} and {{h11}} are both lost. The same happens with the properties of a
> YAML or XML bean, and wherever a map of properties is bound with
> {{PropertyBindingSupport}}.
> Two things combine:
> * {{bindProperties}} sorts the keys with {{PropertyBindingKeyComparator}}:
> first by the number of dots, then references first, then by plain
> {{String.compareTo}}. So the keys of element 10 are bound before those of
> element 1, because the character 0 sorts before the closing bracket.
> * When a key goes through a list element,
> {{getOrCreatePropertyOgnlPathViaReflection}} and
> {{getOrCreatePropertyOgnlPathViaConfigurer}} look the element up with
> {{list.size() > idx ? list.get(idx) : null}}. If there is none, they create
> it and call {{list.add(instance)}}, which appends it at the end of the list,
> whatever the index is.
> With the keys above, element 0 is created at index 0. The host of element 10
> is then appended at index 1 and its port, which still finds no element at
> index 10, at index 2. The keys of element 1 then find the element at index 1
> and overwrite the host {{h10}}, and those of element 2 fill in the port-only
> element at index 2. Element 10 is lost.
> The same append also spreads the keys of one element over several elements
> whenever the index is not the next free position, that is when the elements
> are numbered from 1 or have a gap:
> {noformat}
> servers[1].host=a, servers[1].port=1
> -> [Server{host=a, port=0}, Server{host=null, port=1}]
> expected [null, Server{host=a, port=1}]
> servers[0].host=a, servers[5].host=z, servers[5].port=9
> -> [Server{host=a, port=0}, Server{host=z, port=0}, Server{host=null,
> port=9}]
> expected [Server{host=a, port=0}, null, null, null, null, Server{host=z,
> port=9}]
> {noformat}
> The rest of the list handling already places a value at its index and pads
> with null:
> * a single key such as {{names\[2\]=x}} uses {{ObjectHelper.addListByIndex}},
> * an array property is enlarged to the index,
> * a list whose elements are declared first ({{servers\[3\]=#class:...}}, the
> CAMEL-15396 example) is right, because the declaring key creates the element
> at its index before the nested keys are bound.
> Only an element created for a nested key is appended. Nothing documents that,
> and no test covers more than 2 nested elements.
> *The list key "last"*
> The documentation (property-binding.adoc) and the class javadoc say: "To
> refer to the last element, then use last as key." Every list and array index
> is parsed with {{Integer.parseInt}}, so {{names\[last\]=z}} and
> {{servers\[last\].port=7}} fail with {{NumberFormatException: For input
> string: "last"}}. The sentence has been in the javadoc since 3.0.0 and was
> never implemented. (An empty key in a nested key, {{servers\[\].port}}, does
> refer to the last element, but that is not documented.) The wording probably
> comes from the Simple language, where {{last}} works as a list index.
> h3. Reproduction
> A {{Config}} bean with a list of {{Server}} (host, port) and a list of
> String, bound with {{PropertyBindingSupport.build().bind(context, config,
> map)}} on main: the results above. The first example also fails through Camel
> Main itself: 12 servers bound with
> {{camel.beans.cluster.servers\[i\].host/port}} give 10 servers. The same
> results come with the options Camel Main uses for {{camel.beans}} (mandatory,
> ignore case, remove parameters), and with a configurer that implements
> {{getCollectionValueType}}. A small formal model (Lean 4) of the element
> lookup shows that, for any list and any index beyond its end, two keys with
> the same index are bound on two different elements, and that the new element
> is never at the index of the key. The model does not include the key order,
> so it does not cover the case with 11 or more elements; that one was found by
> a test and then traced through the comparator.
> h3. Affected versions
> The append ({{list.add(instance)}} in both methods) and the string order of
> the keys both came in 3.5.0, with nested list binding (CAMEL-15396), and both
> are in every release since, up to main. {{last}} is documented from 3.0.0 on
> and was never implemented.
> h3. Proposed fix
> * In both getOrCreate methods, create a missing element at its index with
> {{ObjectHelper.addListByIndex(list, idx, instance)}}, as the single key
> already does, and keep {{list.add}} only for the empty key.
> {{addListByIndex}} sets the element when the index is inside the list, so a
> null slot left by padding is filled in place and nothing shifts.
> * Resolve the list key {{last}} to the index of the last element (0 for an
> empty list) where a list index is parsed. Arrays keep numeric indexes. If
> {{last}} is not wanted, the alternative is to remove the sentence from the
> documentation.
> * Documentation: say that an index beyond the end of the list pads it with
> null.
> Sorting the digits inside the brackets as numbers would hide the case with 11
> or more elements, but not the gaps. With the fix above the order of the keys
> no longer matters, so the comparator can stay as it is.
> h3. Compatibility
> A configuration that numbers its elements from 1, or leaves a gap, with one
> key per element ({{servers\[1\].host=a}}, {{servers\[2\].host=b}}) works
> today by accident: the elements are appended and the list has no gap ({{\[a,
> b\]}}). With the fix the list is {{\[null, a, b\]}}, as for a single key or
> an array, and code that iterates the list can hit the null. The pull request
> adds a note to the 4.23 upgrade guide.
> Duplicate check (2026-09-28): JIRA text "PropertyBindingSupport" (42 issues),
> "list index", "nested list", "list binding", "property binding" with index,
> and "camel.beans" with list or array: only CAMEL-15396 (gaps, right for
> declared elements) and CAMEL-15394 (root object with lists) are related,
> neither is about the index of a nested element, the key order or "last". Open
> pull requests: #26983 (CAMEL-25083, camel-support ObjectHelper) and #26984
> (CAMEL-25084, camel-util ObjectHelper) do not change this code or
> {{addListByIndex}}. CAMEL-25009 (In Progress) changed the "#class:"
> parameters in the same file; no overlap.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)