Javax validation of generics in Springboot with Kotlin - spring

I have a controller:
#PostMapping
fun create(
#RequestBody #Valid request: MyContainer<CreateRequest>,
): MyContainer<Dto> = service.create(request.objects)
with MyContainer and CreateRequest looking something like this:
class MyContainer<T>(
#field:Valid // also tried param
#field:NotEmpty(message = "The list of objects can not be null or empty")
var objects: List<#Valid T>? = listOf(),
)
class CreateRequest(
#field:NotNull(message = "Value can not be null")
var value: BigDecimal? = null,
)
In my tests, the "outer" validation works, that is I do get the expected error message if I send it { "objects": null } or { "objects": [] }. But I can not get it to validate the contents of the list. From what I understand in Java List<#Valid T> should work, but for whatever I can not get it to work in kotlin.
I figured I might need some kind of use-site target on #Valid in List<#Valid T>, but I can't find one that's applicable for this use case.
How can I get the validation to work for the list?

I managed to find a solution myself.
Apparently get: is the correct use-site target, not field: or param:. Furthermore the #Valid in List<#Valid T> was not necessary.
For reference, here's the working class (also changed it back to a data class as that doesn't seem to pose an issue).
class MyContainer<T>(
#get:Valid
#get:NotEmpty(message = "The list of objects can not be null or empty")
var objects: List<T>? = listOf(),
)
and the CreateRequest:
class CreateRequest(
#get:NotNull(message = "Value can not be null")
var value: BigDecimal? = null,
)
Changing to the get: use-site target was only necessary for #Valid, but I opted for using it everywhere for consistency and since it seems to be the one that works best.

Related

Can #PathVariable recieve variable defined own orignal Data type?

I made controller method.
I want the method to receive variable defined by own original Data type.
like Below,
data class UserId(
val value: UUID
)
#GetMapping("user/{userId}")
fun getUser(
#PathVariable userId: UserId
) {
userService.getUser(userId)
}
Of course, I know how to receive variable of String.
#GetMapping("user/{userId}")
fun getUser(
#PathVariable userId: String
) {
// I think this code is redundancy.
val id = UserId.fromString(userId)
userService.getUser(userId)
}
Can I receive variable defined own original Data Type?
Do you know any idea?
The main question is, how do you see this working? Would you receive the data class as a serializable JSON object? If so, shouldn't that be inputted as the request body?
If there's another way you envision this working, you can always manually serialize the object later, something like:
Controller:
#GetMapping("user/{userId}")
fun getUser(
#PathVariable userIdSerialized: String
) {
userService.getUser(userIdSerialized)
}
Service:
fun getUser(userIdSerialized: String) {
// Using Jackson
val deserialized: UserId = mapper.readValueFromString(userIdSerialized, UserId::class.java)
}
But again, this should really be a request body.

Polymorphic #RequestBody in Spring-Boot

The problem's pretty straightforward. I have a couple of events that derive from the same interface, and I'd like to deserialize them to their propper super-class.
I know how to do that with an object mapper, but using my own mapper would mean letting Spring-Boot parse the #RequestBody as a String and then doing it myself, which isn't the worlds end, but I can't help but suspect that Spring provides proper tools to handle this kind of situation. Trouble is, I can't seem to find them.
Here's a bit of sample code:
example event:
interface YellowOpsEvent {
val user: String
val partner: String
val subject: String
val change: NatureOfChange
}
data class StatusChangedEvent(override val user: String,
override val partner: String,
override val subject: String,
val before: String,
val after: String): YellowOpsEvent {
override val change = NatureOfChange.Changed
}
controller:
#PostMapping("/event")
fun writeEvent(#RequestBody event: YellowOpsEvent) { // < I expect this not to throw an exception
val bugme = event is StatusChangedEvent // < I expect this to return true if I send the proper event data.
}
Just to clarify, I perfectly understand why this doesn't work out of the box. The trouble is, I can't find out what I need to do to make it work.
The link in pL4Gu33's comment lead me in the right direction, but it took some additional searching and fiddling, plucking information from here and there to arrive at the solution that would finally work, so I'm summarising it here for completeness.
The trouble is that you'll need two annotations, one on the interface and one on the implementing classes, the combined use of which seems somewhat ill-documented.
First, on the interface, add this annotation. Contrary to some tutorials you will find, no further annotation of the interface is required:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
interface YellowOpsEvent {
val user: String
val partner: String
val subject: String
val change: NatureOfChange
}
According to some documentation, this alone should be enough for propper deserialisation. The spring-boot controller, however, will throw an exception because the passed root name does not match the class it was expecting.
// the above will throw an exception when the serialization product is sent to this controller:
#PostMapping("/event")
fun writeEvent(#RequestBody event: YellowOpsEvent) { // < I expect this not to throw an exception
val bugme = event is StatusChangedEvent // < I expect this to return true if I send the proper event data.
}
To fix that, add the #JsonRootName annotation to any implementing classes, with the interface's name. Most documentation of this annotation don't use it for this, instead just for renaming the type, and even when it's mentioned in the linked question in the context of polymorphism, it wrongly uses its own name. This is what it needs to look like:
#JsonRootName("YellowOpsEvent")
data class StatusChangedEvent(override val user: String,
override val partner: String,
override val subject: String,
val before: String,
val after: String): YellowOpsEvent {
override val change = NatureOfChange.Changed
}
Now it works! :)

Swagger 2 UI How to show models that are not explicitly returned by RestController

I'm having following issue, on swagger under Models, i see just abstract Base class that is extended by 3 other classes. My current end point returns Base type of class, because i can have 3 different types returned on one end point.
So basically i have something like this
#MappedSuperclass
#ApiModel(description = "Base Details.")
abstract class BaseClass(
open var id: String? = null,
var prop1: String? = null,
var prop2: String? = null,
var prop3: String? = null,
var prop4: String? = null
)
#ApiModel(description = "Some Specific Details that contains all base properties.")
data class AnotherClass(
val prop4: String,
val prop5: String,
val prop6: Set<Amount>,
val prop7: Set<Amount>,
val prop8: String
) : BaseClass()
#ApiModel(description = "Some more Specific Details that contains all base properties.")
data class OneMoreClass(
val prop4: String,
val prop5: String
) : BaseClass()
And in RestController i have this
#GetMapping
#ApiOperation(value = "End point description", notes = "Notes notes notes.")
fun getSomethingFromDatabase(): List<BaseClass> {
return someService.getData();
}
So issue that i have is on swagger UI, under Models section i see just BaseClass and no other classes at all...
I tried this, because somewhere i seen this example:
#ApiModel(description = "Base Details.", subTypes = {AnotherClass.class})
BaseClass
but this way i have "kotlin" issue, that is saying "name is missing", also i can not do AnotherClass::class...
You will have to add those in the config as below:
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(AnotherClass.class), typeResolver.resolve(OneMoreClass.class))
.....
subTypes is still not completely supported in Swagger 2, still has an open ticket
For your Kotlin config, this is how it should look like:
subTypes = [AnotherClass::class, OneMoreClass::class]
I have just added a sample Kotlin controller for you to refer in my github project. Look for AnimalController.kt & SwaggerConfig for required setup.

Create instance of SpringĀ“s ParameterizedTypeReference in Kotlin

I am trying to learn Kotlin, and test how it works with spring boot. My application is using a mongo database to store data and I have a Jersey resource for retrieving data. I am testing it using spring-boot-test and RestTestTemplate.
The RestTestTemplate has an exchange method which takes a ParameterizedTypeReference. This class has a protected constructor. So the only way I managed to use it from Kotlin was like this:
class ListOfPeople : ParameterizedTypeReference<List<Person>>()
Here is my test-method:
#Test
fun `get list of people`() {
// create testdata
datastore.save(Person(firstname = "test1", lastname = "lastname1"))
datastore.save(Person(firstname = "test2", lastname = "lastname2"))
datastore.save(Person(firstname = "test3", lastname = "lastname2"))
datastore.save(Person(firstname = "test4", lastname = "lastname2"))
val requestEntity = RequestEntity<Any>(HttpMethod.GET, URI.create("/person"))
// create typereference for response de-serialization
class ListOfPeople : ParameterizedTypeReference<List<Person>>() // can this be done inline in the exchange method?
val responseEntity : ResponseEntity<List<Person>> = restTemplate.exchange(requestEntity, ListOfPeople())
assertNotNull(responseEntity)
assertEquals(200, responseEntity.statusCodeValue)
assertTrue( responseEntity.body.size >= 4 )
responseEntity.body.forEach { person ->
println("Found person: [${person.firstname} ${person.lastname}] " +
", born [${person.birthdate}]")
}
}
Is this the correct (or only) way to do this, or is there a better way?
If it helps, here is a link for the whole test: testclass on github
While the answer using object expression is correct and the direct equivalent of the way you do it in Java, reified type parameters allow you to simplify it if you need many ParameterizedTypeReferences:
inline fun <reified T> typeReference() = object : ParameterizedTypeReference<T>() {}
// called as
restTemplate.exchange(requestEntity, typeReference<List<Person>>())
When the compiler sees a typeReference<SomeType> call, it's replaced by the definition, so the result is the same as if you wrote object : ParameterizedTypeReference<SomeType>() {}.
Thanks to JB Nizet who pointed me to the correct documentation.
val responseEntity : ResponseEntity<List<Person>> =
restTemplate.exchange(requestEntity,
object: ParameterizedTypeReference<List<Person>> () {})
If I read correctly this is called an Object expression.

Kotlin not nullable value can be null?

I have backend that return me some json.
I parse it to my class:
class SomeData(
#SerializedName("user_name") val name: String,
#SerializedName("user_city") val city: String,
var notNullableValue: String
)
Use gson converter factory:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ENDPOINT)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
And in my interface:
interface MyAPI {
#GET("get_data")
Observable<List<SomeData>> getSomeData();
}
Then I retrieve data from the server (with rxJava) without any error. But I expected an error because I thought I should do something like this (to prevent GSON converter error, because notNullableValue is not present in my JSON response):
class SomeData #JvmOverloads constructor(
#SerializedName("user_name") val name: String,
#SerializedName("user_city") val city: String,
var notNullableValue: String = ""
)
After the data is received from backend and parsed to my SomeData class with constructor without def value, the value of the notNullableValue == null.
As I understand not nullable value can be null in Kotlin?
Yes, that is because you're giving it a default value. Ofcourse it will never be null. That's the whole point of a default value.
Remove ="" from constructor and you will get an error.
Edit: Found the issue. GSON uses the magic sun.misc.Unsafe class which has an allocateInstance method which is obviously considered very unsafe because what it does is skip initialization (constructors/field initializers and the like) and security checks. So there is your answer why a Kotlin non-nullable field can be null. Offending code is in com/google/gson/internal/ConstructorConstructor.java:223
Some interesting details about the Unsafe class: http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
Try to override constructor like this:
class SomeData(
#SerializedName("user_name") val name: String,
#SerializedName("user_city") val city: String,
var notNullableValue: String = "") {
constructor() : this("","","")
}
Now after server response you can check the notNullableValue is not null - its empty

Resources