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

Andrea Cosentino updated CAMEL-26589:
-------------------------------------
    Affects Version/s: 4.22.2
                           (was: 4.22.0)

> Data type contract fails on byte[] bodies, breaking kamelets with dataTypes
> ---------------------------------------------------------------------------
>
>                 Key: CAMEL-26589
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26589
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core, camel-kamelet, camel-yaml-dsl
>    Affects Versions: 4.18.1, 4.19.0, 4.22.2
>            Reporter: Andrea Cosentino
>            Assignee: Guillaume Nodet
>            Priority: Major
>
> When a route has a data type contract ({{inputType}} / {{outputType}}) and 
> the message body is a Java array such as {{byte[]}}, {{ContractAdvice}} fails 
> the exchange even though no conversion is needed:
> {noformat}
> org.apache.camel.CamelExecutionException: Failed to convert body to 'java:[B' 
> content using type conversion for null on the exchange: Exchange[...]
>       at 
> org.apache.camel.processor.transformer.TypeConverterTransformer.transform(TypeConverterTransformer.java:73)
>       at 
> org.apache.camel.processor.ContractAdvice.convertIfRequired(ContractAdvice.java:157)
>       at 
> org.apache.camel.processor.ContractAdvice.doTransform(ContractAdvice.java:127)
>       at 
> org.apache.camel.processor.ContractAdvice.after(ContractAdvice.java:101)
>       ...
> Caused by: java.lang.ClassNotFoundException: [B
>       at 
> org.apache.camel.impl.engine.DefaultClassResolver.resolveMandatoryClass(DefaultClassResolver.java:136)
>       at 
> org.apache.camel.processor.transformer.TypeConverterTransformer.transform(TypeConverterTransformer.java:61)
> {noformat}
> Since CAMEL-23174 (4.18.1, 4.19.0), {{KameletRoutesBuilderLoader}} turns a 
> Kamelet's {{spec.dataTypes.in/out}} into the route {{inputType}} / 
> {{outputType}}. Every Kamelet that declares {{dataTypes}} therefore hits this 
> whenever its body is an array, which is the normal case for binary sources 
> such as {{aws-s3-source}}.
> h3. Cause
> # When a route has a contract, {{RouteReifier}} calls 
> {{camelContext.setUseDataType(true)}}. From then on 
> {{MessageSupport.setBody}} tags every body with {{new 
> DataType(body.getClass())}}. For a {{byte[]}} body that is {{java:\[B}}, 
> because {{byte[].class.getName()}} is {{\[B}}.
> # {{ContractAdvice.after}} -> {{doTransform}} -> {{convertIfRequired(message, 
> from)}} with {{from = java:\[B}} builds a 
> {{TypeConverterTransformer(DataType)}}, which calls 
> {{ClassResolver.resolveMandatoryClass("\[B")}} before it checks whether the 
> body already has that type.
> # {{DefaultClassResolver}} -> {{ObjectHelper.loadClass}} only uses 
> {{ClassLoader.loadClass(name)}}, which cannot load an array class from its 
> binary name ({{Class.forName("\[B", false, loader)}} can). 
> {{ObjectHelper.loadSimpleType}} knows {{byte[]}} but not {{\[B}}. The lookup 
> fails and the exchange is marked failed.
> This logic is unchanged between 4.18.0 and 4.22.0 (code inspection), so 
> explicit route contracts with array bodies presumably fail on older versions 
> too. What CAMEL-23174 changed is that every Kamelet declaring {{dataTypes}} 
> now gets such a contract.
> h3. Reproducer (Camel 4.22.0, plain JVM)
> {code:java}
> from("direct:contract").outputType("application-octet-stream").log("in 
> contract route");
> Exchange e = template.send("direct:contract", ex -> 
> ex.getMessage().setBody("hello".getBytes()));
> // e.getException(): CamelExecutionException: Failed to convert body to 
> 'java:[B' ... Caused by: ClassNotFoundException: [B
> // the same route with a String body succeeds
> {code}
> Kamelet variant, loaded from {{classpath:kamelets/bytes-source.kamelet.yaml}} 
> and consumed with {{from("kamelet:bytes-source")}}. Sending a {{byte[]}} to 
> {{direct:kamelet-in}} fails the same way; a {{String}} body succeeds.
> {noformat}
> apiVersion: camel.apache.org/v1
> kind: Kamelet
> metadata:
>   name: bytes-source
> spec:
>   definition:
>     title: "Bytes Source"
>     properties: {}
>   dataTypes:
>     out:
>       default: binary
>       types:
>         binary:
>           format: "application-octet-stream"
>           mediaType: application/octet-stream
>   template:
>     from:
>       uri: "direct:kamelet-in"
>       steps:
>       - to: "kamelet:sink"
> {noformat}
> h3. Impact
> * The contract is checked when the Kamelet route completes, so the steps 
> after {{kamelet:sink}} run first; only then is the exchange marked failed and 
> the consumer takes its failure path.
> * Verified with {{aws-s3-source}} (Kamelets 4.22.0, default 
> {{includeBody=true}}, so the body is {{byte[]}}): every exchange fails at the 
> end of the Kamelet route, {{AWS2S3Consumer}} runs {{processRollback}} instead 
> of {{processCommit}}, {{deleteAfterRead}} never deletes, and the same objects 
> are consumed again on every poll. It is logged at WARN only ("Exchange 
> failed, so rolling back message status"), so it is easy to miss. Reported 
> downstream in 
> [apache/camel-kafka-connector#1849|https://github.com/apache/camel-kafka-connector/issues/1849].
> * 19 Kamelets in the 4.22.0 catalog declare {{dataTypes}}. The sources with a 
> binary default output are the most likely to carry {{byte[]}}: aws-s3-source, 
> azure-eventhubs-source, azure-storage-blob-source, 
> azure-storage-datalake-source, azure-storage-files-source, 
> google-storage-source and ibm-cos-source. Only aws-s3-source was verified.
> h3. Possible fixes
> * Resolve binary array names in {{ObjectHelper.loadClass}} / 
> {{DefaultClassResolver}}, for example by falling back to 
> {{Class.forName(name, false, loader)}} for names starting with {{\[}}.
> * Keep the {{Class}} in {{DataType(Class)}}, so {{TypeConverterTransformer}} 
> does not have to resolve it by name.
> * Skip the conversion in {{TypeConverterTransformer}} / 
> {{ContractAdvice.convertIfRequired}} when the body is already an instance of 
> the requested type.
> Tests should cover a {{byte[]}} body for both a route contract and a Kamelet 
> with {{dataTypes}}.
> h3. Workaround
> Use the component endpoint directly instead of the Kamelet, so no contract is 
> added (verified with {{aws2-s3:...?deleteAfterRead=true}}).
> ----
> _Claude Code on behalf of Andrea Cosentino_



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

Reply via email to