Open djechelon opened 2 years ago
Thank you for the attention. Is it worth investigating a way to determine the target class based on the field's declaration?
Thank you for the detailed report. However, we would prefer to have that code in a complete sample with minimal dependencies (i.e. no lombok, Spring Security, or other unrelated libraries). Something that we can unzip or git clone, build, and deploy, and that reproduces the problem.
If you want your issue to be considered, please try to provide a more focused description. A snippet of a test class with 10 methods and 200 lines of code is just not helpful.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Repository available at https://github.com/djechelon/spring-poc-27760
The repository contains a test controller (which basically does nothing) and a test class FilterArgumentResolverWebTest
. I used only the testing class to operate the project. I actually never cared about starting the thing (in a real TDD approach).
Please note that while discussing about enums, I have also implemented an interesting test about OffsetDateTime
unparseable.
As stated in the original post, you will find that:
public static class AmlObjectTypeFilter extends SimpleFilter<AmlObjectType>
then Spring successfully takes AmlObjectType
as type of the property and decodes CONTROL
string properlySimpleFilter<AmlObjectType>
the property is set with a String valueAbout item 2, there is yet another test resolveArgument_lookAtTheCast
where the invocation succeeds (i.e. 204 code) but then you can't get the item as an enum. It is particularly required for the real case of the filter framework when these parameters need to be passed to the persistence layer.
The ultimate goal of the SimpleFilter
class & company is to allow table filtering at some degree of dynamicity, e.g. it's cool to invoke ?author.eq=djechelon
in an MVC controller
@djechelon I've looked at the repository and played a little bit with it and I am afraid we're in the same state as what Rossen described above. It looks like the sample as a lot of unnecessary noise. Can you please trim it down so that it's obvious what you're trying to do?
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Okay, let me try to summarize and update the repository whith what's important. Since then, I have changed job and I don't have any more business interest in this
Goal is to have Spring read query string parameters into a complex object. In the old business scenario, it was a filter object for queries.
Sample requests include
GET /data?name.eq=John&surname.in=Doe&surname.in=Roe
By design, I leverage a generic SimpleFilter<T>
where T can be anything but is very well declared at compile time.
I am having problems with enums in particular. I have added a test case in which you can see that if you POST the filter and let Jackson handle it, it will decode it fine (I have left only String filter and Enum filter in the sample repository). Jackson decodes string literal CONTROL
into enum AmlObjectType.CONTROL
.
Spring REST does not decode that literal into an enum, but rather reflects "CONTROL"
string literal into the target object
Expected: hasProperty("exampleObjectType", hasProperty("eq", an instance of org.zighinetto.springpoc.AmlObjectType))
but: property 'exampleObjectType' property 'eq' "CONTROL" is a java.lang.String
If I try to get the "eq" value for constructing a filter in runtime code, I will get a ClassCastException.
Repo updated
@rstoyanchev @snicoll Maybe the difference of behavior could be related to the fact that in @ModelAttribute
+ GET use case, GenericTypeResolver#resolveType
is not invoked, while for @RequestBody
+ POST use case, it is via AbstractJackson2HttpMessageConverter#getJavaType
.
The difference comes from every nested bean being independently bound: Once the outer ExampleTestFilter
instance is dereferenced to the inner SimpleFilter
field, further binding occurs on that SimpleFilter
instance - resolving type variables against its class but not taking into account where that instance came from, so losing the relationship to the field and its generic declaration in the outer class.
We can try to revisit this for 6.2 but it won't be straightforward since standard JavaBeans introspection operates against a Class.
it won't be straightforward since standard JavaBeans introspection operates against a Class.
Correct. Jackson works against a Type
(e.g. TypedReference
), so this explains why this works for the POST payload
Affects: 5.3.10
I am experiencing a problem with MVC method argument resolving in GET invocations. I have already reduced my problem to a bunch of classes and a reproducible example using Spring Test.
The idea is that I have a Filter Framework that heavily relies on generics. The first part of the FF, the one we are dealing with, is MVC argument resolution. The second part is translation into ORM predicates, which is out of scope.
Let's not talk about
IntegerFilter
andLongFilter
, let's focus on a genericEnumFilter
I know that Java erases generic types to the lower bound (
java.lang.Enum
), but the generic information is preserved in Java 11 when applied to a field.Consider this particular filter
Consider the following controller:
Now consider the following tests
The problems here
If I declare (even as a static class) a boilerplate filter class bound to the
AmlObjectType
enum, it works. But if I declare a field asEnumFilter<AmlObjectType>
Spring is unable to resolve the property as it thinks the property value should be cast toEnum
, which is the generic bound ofEnumFilter<T extends Enum<T>>
.E.g. using
GET /api?exampleObjectTypeEnum.eq=CONTROL
works, usingGET /api?exampleObjectTypeEnumGeneric.eq=CONTROL
orGET /api?exampleObjectTypeSimple.eq=CONTROL
does not.It would not happen using JSON POST with Jackson Object Mapper.
In this case, using
SimpleFilter<AmlObjectType>
results in Spring setting the value CONTROL (String) as an Object into the property, which fails the assertion because I am comparing an enum with a string.In this case, Spring detects the target type as
Enum
, and can't cast CONTROL to the root Enum classThis succeeds because target property is of type
AmlObjectTypeFilter
which is bound directly toAmlObjectType
Debugging
I also found that Spring could be aware of the actual generic type of a field at some point. I was spelunking deep into
Introspector
and found the belowThis shows that the generic type is known for the field, but then looks like Spring ignores the property generic binding and works only on the (raw....) type.
At
AbstractNestablePropertyAccessors#
variable ph has now lost any information about the generic binding of the filter class's property.And at line 590 the "target class" is now just
Enum
.Porkarounds