You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@camel.apache.org by Bob Paulin <bo...@bobpaulin.com> on 2021/01/29 14:50:08 UTC

[Discuss] How to handle UnmarshalType defined as an Array Class in Java DSL

Hi,

I've submitted a issue [1].

in prior versions you could define an array type as the unmarshalType in 
a dataformat in the Java DSL as follows:

from("direct:beginArray").unmarshal().json(JsonLibrary.Jackson, 
String[].class).to("mock:endArray");

This now throws an exception see [1].


It appears is due to the code in JsonDataFormatReifier [2] that converts 
the unmarshalType into unmarshalTypeName and then the data format (in my 
case JacksonDataFormat) tries to resolve the class via the camel 
ClassResolver

camelContext.getClassResolver().resolveClass(unmarshalType);

Given this delegates to a classloader loadClass method see [3]. 
Classloaders can't load array types this way.  Also it fails the check 
on the original unmarshalType because the original unmarshalType in the 
definition is not copied to the created DataFormat object so the check

if (unmarshalTypeName != null && (unmarshalType == null || unmarshalType 
== Object.class)) {

does not help here.


If arrays are to be supported I see a couple of options.

1) org.apache.camel.util.ObjectHelper could have a fall back to use 
something like the Apache Commons ClassUtil.getClass().  This would also 
allow folks using unmarshalTypeName to specify java.lang.String[] and 
get a result.  However it does make the ClassResolver act less like a 
classloader which may be undesirable.

2) Detect that the unmarshalTypeName is an array and resolve the base 
class then convert to an array.  Array.newInstance(..).getClass() may be 
replaced with class.arrayType() in Java 12+.  Might be a little ugly but 
leaves ObjectHelper alone.

Ex
@Override
     protected void doInit() throws Exception {
         if (unmarshalTypeName != null && (unmarshalType == null || 
unmarshalType == Object.class)) {

             String baseUnmarshalType = unmarshalTypeName;
             if (baseUnmarshalType.startsWith("[L")) {
                 baseUnmarshalType = baseUnmarshalType.substring(2, 
baseUnmarshalType.length() - 1);
                 Class<?> baseUnmarshalTypeClass = 
camelContext.getClassResolver().resolveClass(baseUnmarshalType);
                 unmarshalType = 
Array.newInstance(baseUnmarshalTypeClass, 0).getClass();
             } else {
                 unmarshalType = 
camelContext.getClassResolver().resolveClass(baseUnmarshalType);
             }

......
}

3) Allow the unmarshalType to be passed directly to the newly created 
DataFormat.  I know these fields are transient currently (and have been 
for a long time) but if it were pass the check to do the class 
resolution would not need to run and it could just use the unmarshalType 
directly.

4) Don't allow array types and throw an exception.  Seems a bit 
opinionated but better than the null error.


Other options?

Sincerely,

Bob

[1] https://issues.apache.org/jira/browse/CAMEL-16114
[2] 
https://github.com/apache/camel/blob/7a58c2b0f9612c205ddbafbe30a8487f46021d14/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java#L46
[3] 
https://github.com/apache/camel/blob/7a58c2b0f9612c205ddbafbe30a8487f46021d14/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java#L474

Re: [Discuss] How to handle UnmarshalType defined as an Array Class in Java DSL

Posted by Bob Paulin <bo...@bobpaulin.com>.
Hi,

Zoran thanks for the reply.  I saw Claus applied a patch over the 
weekend that fixed my test but it turns out my test may have been too 
simple since String[] has some custom code to help it in the 
ObjectHelper. I've attached a  case to CAMEL-16114 that uses a POJO that 
still fails with the new code.  I would also mention with #1 we do not 
need to add a dependency if we use similar behavior like Class.forName 
to load the array type.  But the issue of this not being ClassLoader 
like behavior remains so I'm still not sure which path forward makes the 
most sense.  Happy to help, please let me know if it makes sense to 
re-open CAMEL-16114 or if it would would be better to submit a new issue.

- Bob

On 2/1/2021 3:48 AM, Zoran Regvart wrote:
> Hi Bob, Cameleers
> just to note, this got marked as spam in my mail client (for whatever
> reason), so you might have missed this also. Some comments inline:
>
> On Fri, Jan 29, 2021 at 3:50 PM Bob Paulin <bo...@bobpaulin.com> wrote:
>> Hi,
>>
>> I've submitted a issue [1].
>>
>> in prior versions you could define an array type as the unmarshalType in
>> a dataformat in the Java DSL as follows:
>>
>> from("direct:beginArray").unmarshal().json(JsonLibrary.Jackson,
>> String[].class).to("mock:endArray");
>>
>> This now throws an exception see [1].
>>
>>
>> It appears is due to the code in JsonDataFormatReifier [2] that converts
>> the unmarshalType into unmarshalTypeName and then the data format (in my
>> case JacksonDataFormat) tries to resolve the class via the camel
>> ClassResolver
>>
>> camelContext.getClassResolver().resolveClass(unmarshalType);
>>
>> Given this delegates to a classloader loadClass method see [3].
>> Classloaders can't load array types this way.  Also it fails the check
>> on the original unmarshalType because the original unmarshalType in the
>> definition is not copied to the created DataFormat object so the check
>>
>> if (unmarshalTypeName != null && (unmarshalType == null || unmarshalType
>> == Object.class)) {
>>
>> does not help here.
>>
>>
>> If arrays are to be supported I see a couple of options.
>>
>> 1) org.apache.camel.util.ObjectHelper could have a fall back to use
>> something like the Apache Commons ClassUtil.getClass().  This would also
>> allow folks using unmarshalTypeName to specify java.lang.String[] and
>> get a result.  However it does make the ClassResolver act less like a
>> classloader which may be undesirable.
> In addition to that, with the efforts on making Camel core lighter, I
> don't think we want to add a new dependency to `camel-util`.
>
>> 2) Detect that the unmarshalTypeName is an array and resolve the base
>> class then convert to an array.  Array.newInstance(..).getClass() may be
>> replaced with class.arrayType() in Java 12+.  Might be a little ugly but
>> leaves ObjectHelper alone.
>>
>> Ex
>> @Override
>>       protected void doInit() throws Exception {
>>           if (unmarshalTypeName != null && (unmarshalType == null ||
>> unmarshalType == Object.class)) {
>>
>>               String baseUnmarshalType = unmarshalTypeName;
>>               if (baseUnmarshalType.startsWith("[L")) {
>>                   baseUnmarshalType = baseUnmarshalType.substring(2,
>> baseUnmarshalType.length() - 1);
>>                   Class<?> baseUnmarshalTypeClass =
>> camelContext.getClassResolver().resolveClass(baseUnmarshalType);
>>                   unmarshalType =
>> Array.newInstance(baseUnmarshalTypeClass, 0).getClass();
>>               } else {
>>                   unmarshalType =
>> camelContext.getClassResolver().resolveClass(baseUnmarshalType);
>>               }
>>
>> ......
>> }
>>
>> 3) Allow the unmarshalType to be passed directly to the newly created
>> DataFormat.  I know these fields are transient currently (and have been
>> for a long time) but if it were pass the check to do the class
>> resolution would not need to run and it could just use the unmarshalType
>> directly.
> This looks like the best option to me, we should not be loosing type
> information.
>
>> 4) Don't allow array types and throw an exception.  Seems a bit
>> opinionated but better than the null error.
>>
>>
>> Other options?
>>
>> Sincerely,
>>
>> Bob
>>
>> [1] https://issues.apache.org/jira/browse/CAMEL-16114
>> [2]
>> https://github.com/apache/camel/blob/7a58c2b0f9612c205ddbafbe30a8487f46021d14/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java#L46
>> [3]
>> https://github.com/apache/camel/blob/7a58c2b0f9612c205ddbafbe30a8487f46021d14/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java#L474
>
>

Re: [Discuss] How to handle UnmarshalType defined as an Array Class in Java DSL

Posted by Zoran Regvart <zo...@regvart.com>.
Hi Bob, Cameleers
just to note, this got marked as spam in my mail client (for whatever
reason), so you might have missed this also. Some comments inline:

On Fri, Jan 29, 2021 at 3:50 PM Bob Paulin <bo...@bobpaulin.com> wrote:
>
> Hi,
>
> I've submitted a issue [1].
>
> in prior versions you could define an array type as the unmarshalType in
> a dataformat in the Java DSL as follows:
>
> from("direct:beginArray").unmarshal().json(JsonLibrary.Jackson,
> String[].class).to("mock:endArray");
>
> This now throws an exception see [1].
>
>
> It appears is due to the code in JsonDataFormatReifier [2] that converts
> the unmarshalType into unmarshalTypeName and then the data format (in my
> case JacksonDataFormat) tries to resolve the class via the camel
> ClassResolver
>
> camelContext.getClassResolver().resolveClass(unmarshalType);
>
> Given this delegates to a classloader loadClass method see [3].
> Classloaders can't load array types this way.  Also it fails the check
> on the original unmarshalType because the original unmarshalType in the
> definition is not copied to the created DataFormat object so the check
>
> if (unmarshalTypeName != null && (unmarshalType == null || unmarshalType
> == Object.class)) {
>
> does not help here.
>
>
> If arrays are to be supported I see a couple of options.
>
> 1) org.apache.camel.util.ObjectHelper could have a fall back to use
> something like the Apache Commons ClassUtil.getClass().  This would also
> allow folks using unmarshalTypeName to specify java.lang.String[] and
> get a result.  However it does make the ClassResolver act less like a
> classloader which may be undesirable.

In addition to that, with the efforts on making Camel core lighter, I
don't think we want to add a new dependency to `camel-util`.

> 2) Detect that the unmarshalTypeName is an array and resolve the base
> class then convert to an array.  Array.newInstance(..).getClass() may be
> replaced with class.arrayType() in Java 12+.  Might be a little ugly but
> leaves ObjectHelper alone.
>
> Ex
> @Override
>      protected void doInit() throws Exception {
>          if (unmarshalTypeName != null && (unmarshalType == null ||
> unmarshalType == Object.class)) {
>
>              String baseUnmarshalType = unmarshalTypeName;
>              if (baseUnmarshalType.startsWith("[L")) {
>                  baseUnmarshalType = baseUnmarshalType.substring(2,
> baseUnmarshalType.length() - 1);
>                  Class<?> baseUnmarshalTypeClass =
> camelContext.getClassResolver().resolveClass(baseUnmarshalType);
>                  unmarshalType =
> Array.newInstance(baseUnmarshalTypeClass, 0).getClass();
>              } else {
>                  unmarshalType =
> camelContext.getClassResolver().resolveClass(baseUnmarshalType);
>              }
>
> ......
> }
>
> 3) Allow the unmarshalType to be passed directly to the newly created
> DataFormat.  I know these fields are transient currently (and have been
> for a long time) but if it were pass the check to do the class
> resolution would not need to run and it could just use the unmarshalType
> directly.

This looks like the best option to me, we should not be loosing type
information.

>
> 4) Don't allow array types and throw an exception.  Seems a bit
> opinionated but better than the null error.
>
>
> Other options?
>
> Sincerely,
>
> Bob
>
> [1] https://issues.apache.org/jira/browse/CAMEL-16114
> [2]
> https://github.com/apache/camel/blob/7a58c2b0f9612c205ddbafbe30a8487f46021d14/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java#L46
> [3]
> https://github.com/apache/camel/blob/7a58c2b0f9612c205ddbafbe30a8487f46021d14/core/camel-util/src/main/java/org/apache/camel/util/ObjectHelper.java#L474



-- 
Zoran Regvart