[ 
https://issues.apache.org/jira/browse/CAMEL-25092?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-25092.
---------------------------------
    Resolution: Fixed

Fixed by https://github.com/apache/camel/pull/26993 (merged as 6e6282c2e7b5).

_Claude Code on behalf of davsclaus_

> 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)

Reply via email to