[ 
https://issues.apache.org/jira/browse/CAMEL-26782?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18141255#comment-18141255
 ] 

Guillaume Nodet commented on CAMEL-26782:
-----------------------------------------

Fix implemented and PR opened: 
[apache/camel-spring-boot#2079|https://github.com/apache/camel-spring-boot/pull/2079]

*Root Cause Confirmed*

{{copyConfigurationProperties}} ({{CamelPropertiesHelper}}) calls {{removeIf}} 
to keep only properties recorded in {{BoundConfigurationProperties}}. That 
tracker only records values bound from Spring Boot application properties — 
values assigned programmatically in Java are invisible to it and were silently 
dropped.

*Fix*

Before removing a property, compare its current value against a 
freshly-constructed default instance of the same configuration class (via its 
no-arg constructor). If the value differs from the default, it was assigned 
programmatically and is copied to the target. Values that equal the class 
default and are not Spring-bound are still dropped — preserving the CAMEL-24501 
fix for catalog defaults overwriting user-supplied beans.

If the configuration class has no accessible no-arg constructor, the fallback 
is an empty defaults map, which conservatively treats all non-bound values as 
programmatic assignments.

*Tests Added*

- {{testCopyConfigurationPropertiesCopiesProgrammaticallyAssignedValue}} — a 
value set in Java on the config bean is copied to the target
- 
{{testCopyConfigurationPropertiesDoesNotCopyProgrammaticValueMatchingDefault}} 
— a value set in Java that equals the class default is NOT copied (the target's 
own value is preserved)

_This comment was posted by an AI coding agent (Hermes) on behalf of Guillaume 
Nodet._

> camel-spring-boot: copyConfigurationProperties drops values assigned in Java 
> to a starter configuration bean
> ------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26782
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26782
>             Project: Camel
>          Issue Type: Bug
>          Components: tooling
>    Affects Versions: 4.23.0
>            Reporter: Salvatore Mongiardo
>            Assignee: Guillaume Nodet
>            Priority: Major
>             Fix For: 4.22.3, 4.24.0
>
>
> h3. Problem
> CAMEL-24501 (apache/camel-spring-boot#1951, commit 72323d48) changed 
> {{CamelPropertiesHelper.copyConfigurationProperties}} to copy only the 
> options that Spring Boot recorded in {{BoundConfigurationProperties}}. That 
> fixes catalog defaults overwriting values on user supplied Camel beans, but 
> {{isConfigured()}} can only recognise values bound from properties. A value 
> assigned in Java to the generated starter configuration bean before the 
> customizer runs is no longer copied to the component. 
> {{camel.springboot.lenient-configuration-binding=true}} does not restore it. 
> The change is released: {{camel-spring-boot-4.23.0}} contains it.
> h3. Reproduction
> Using {{HttpComponentConfiguration}} and {{HttpComponent}}:
> {code:java}
> configuration.setSkipRequestHeaders(true);   // before the customizer runs
> {code}
> {code}
> camel.component.http.log-http-activity=true
> {code}
> Before #1951 both values reach the component. After it, {{logHttpActivity}} 
> is {{true}} but {{skipRequestHeaders}} stays {{false}}.
> h3. In-tree cases of the same regression
> * {{SpringBootPlatformWebMvcConfiguration}} (main) writes 
> {{spring.mvc.async.request-timeout}} into 
> {{PlatformHttpComponentConfiguration.requestTimeout}} from code, so it no 
> longer reaches {{PlatformHttpComponent}} (small impact, Spring MVC still 
> enforces the timeout). Good test for the fix.
> * {{SpringKafkaPropertiesAutoConfiguration}} (camel-spring-boot-4.22.x only) 
> writes the {{spring.kafka.*}} values into {{KafkaComponentConfiguration}} 
> from a {{@PostConstruct}}. The filter drops them. On main the bridge is a 
> {{ComponentCustomizer}} (e7850714524, precedence fixed in 115f626f4e7 / 
> CAMEL-22760), so 4.22.x must not take #1951 as is.
> h3. Related effect: defaults moved from catalog to the component's own
> Any option the application does not configure now keeps the component's Java 
> default instead of the catalog default, where the two differ. Example: 
> {{XsltComponent.xpathTotalOpLimit}} is an {{int}} with {{defaultValue = 
> "10000"}} in its metadata, the field is 0, the starter initializes 10000. 
> Before #1951 the starter copied 10000. Also, 
> {{BaseOptionModel.resolveDefaultValue()}} turns a missing default into 
> {{false}} for {{java.lang.Boolean}}, so many nullable Booleans go from 
> {{false}} to {{null}}. Only 8 of the 607 {{java.lang.Boolean}} options in the 
> 4.22.2 component catalogs have no default.
> h3. Why it was noticed
> Red Hat CSB-13052: with {{camel-ai-tool-starter}} + 
> {{camel-mcp-server-starter}} on 4.22.1, the catalog {{defaultValue: false}} 
> for the nullable MCP hints ({{readOnlyHint}}, {{destructiveHint}}, 
> {{idempotentHint}}, {{openWorldHint}}) is materialised as {{= false}} in 
> {{AiToolComponentConfiguration}}, copied onto {{AiToolComponent}}, and 
> published in MCP {{tools/list}} as explicit {{false}} annotations although 
> the route sets none. #1951 resolves this on main, so the fix for the 
> regression must keep the hints unset.
> h3. Proposed fix
> *main (4.24.0), option A:* in {{copyConfigurationProperties}} copy an option 
> if it was bound or if its value differs from the value on a freshly 
> constructed instance of the same configuration class (computed once per 
> class). A catalog default equals the fresh instance's value, so it is not 
> copied; a Java assignment differs, so it is copied.
> * Collections: the only object valued initializer in the generated classes is 
> {{serverConfig = new HashMap<>()}} (Jolokia), and two empty maps compare 
> equal; collections without initializer are {{null}} on a fresh instance.
> * Fallback when the instance cannot be created (should not happen, generated 
> classes have a no-arg constructor): copy everything, as {{boundProperties()}} 
> does, but fail only on options known to be configured. #1951 removed the 
> DEBUG-only branch for options that carry just their default, so copying 
> everything would otherwise fail startup.
> * Known gap, to document, also in the paragraph #1951 added to 
> {{starter-configuration.adoc}}: a Java assignment equal to the catalog 
> default (e.g. {{setSkipRequestHeaders(false)}} onto a user bean holding 
> {{true}}) is not copied.
> * Smaller points: copy {{BoundConfigurationProperties.getAll()}} (live view) 
> inside the {{try}}; check {{BoundConfigurationProperties.get()}} for {{null}} 
> instead of catching the NPE; log skipped options at DEBUG; restore coverage 
> in {{testCopyConfigurationPropertiesIgnoresDefaultThatCannotBeSet}} (uses 
> {{configWithoutDrift()}}) and 
> {{testCopyConfigurationPropertiesIgnoresAutoConfigurationOptions}} (does not 
> bind {{enabled}}/{{customizer}}).
> *camel-spring-boot-4.22.x (4.22.3), option C:* do not backport #1951 (it 
> changes every starter on a patch branch). Do not initialize the four nullable 
> MCP hint fields of {{camel-ai-tool}} in {{SpringBootAutoConfigurationMojo}}; 
> {{returnDirect}} keeps its declared {{false}} default. Only the ai-tool 
> starter changes.
> h3. Tests
> * {{HttpComponentConfiguration}} scenario above (Java assignment plus a bound 
> value).
> * ai-tool hints stay unset on {{AiToolComponent}} and in the MCP 
> {{tools/list}} output.
> * A bound value equal to the catalog default is still copied onto a user bean.
> * platform-http {{requestTimeout}} from {{spring.mvc.async.request-timeout}}.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to