Ktorm entities as springboot controller parameters - java

I'm trying to use Ktorm in my new springboot application, and get myself into problem when trying to use Ktorm entities interfaces as springboot controller parameters.
The entity and Controller look like this:
// Controller definition
#RestController
class TaskController() {
#PostMapping
fun addTask(
#RequestBody task: Task
): Long {
// ... do something with `task`
}
}
// Entity definition (unncessary properties are omitted)
interface Task : Entity<Task> {
var id: Long
var title: String
var content: String
companion object : Entity.Factory<Task>()
}
I got this exception once calling function addTask():
[HttpMessageConversionException]
Type definition error: [simple type, class website.smsqo.entity.Task]; nested exception is:
[com.fasterxml.jackson.databind.exc.InvalidDefinitionException]
Cannot construct instance of website.smsqo.entity.Task (no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 1, column: 1]"
}
(Paramter task is posted from front-end by RequestBody)
I think maybe the reason is that, as an interface, springboot can't find a proper way to initialize Task. However, refactoring it into this is surely not an elegant solution:
#RestController
class TaskController() {
#PostMapping
fun addTask(
id: Long, title: String, content: String // even more fields...
): Long {
val task = Task {
this.id = id
this.title = title
this.content = content
}
// ... do something with `task`
}
}
Any better solution proposed? Thanks for your reply in advance!

Well, it turns out that solution was noted explicitly in documents provided by Ktorm:
// extracted from org.ktorm.entity.Entity
/*
* Besides of JDK serialization, the ktorm-jackson module also supports serializing entities in JSON format. This
* module provides an extension for Jackson, the famous JSON framework in Java word. It supports serializing entity
* objects into JSON format and parsing JSONs as entity objects. More details can be found in its documentation.
*/
Implementing org.ktorm:ktorm-jackson:3.4.1 brings us a Jackson Module, named KtormModule in package org.ktorm.jackson. What we need to do next is applying the module to our springboot application as in class annotated by #Configuration:
#Configuration
class KtormConfig {
#Bean
fun ktormJacksonModule(): Module = KtormModule()
// ... and other configurations if you like
}
And that's it. Such KtormModule will be discovered and applied by jackson on springboot application launches, after which there's no more problem encoding and decoding between json and Ktorm Entities.

Related

Create generalised update endpoint with Kotlin and Spring Boot

I'm working on a general CRUD controller for my Spring Boot app so that I don't have to implement crud methods for every resource. This is my function so far:
#PatchMapping(ENTITY_URL)
fun update(#PathVariable("id") entity: E, #RequestBody fields: Map<String,Any>): E {
entity::class.memberProperties
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
fields[prop.name]?.let {
// Class.forName(prop.returnType.toString())
val value = objectMapper.readValue(it.toString())
prop.setter.call(entity,value)
}
}
repo.save(entity)
return entity
}
Before I was passing it directly to the setter and it worked fine for primitive types. The problem comes with more complicated properties. For example my user entity has a roles property of type Set<Role>. Of course spring does not know anything about my fields's context so this role:[{id:1}] is transformed to an ArrayList.
What I was trying to do with the code above is to deserialise a field manually using the type of the property we work with but can't get it to work. The code as it is now obviously gives compile time error because it can not figure out the type, I know the second parameter can be a type but can't convert my type to a Java class. Also my set is of Kotlin Set not Java Set so Class.forName does not work either.
The I found the Kotlin module for the Object mapper and added it to my build.gradle.kt but still I can't figure out how to provide the type

Spring Boot Jackson defaultImpl with JsonTypeInfo

I am trying to handle all types of Jackson exceptions that turn up during REST API requests in a Spring Boot application. If something cannot be serialized, JsonMappingException is thrown. I handle this exception, build the field path that cannot be serialized (using exception.getPath) and return this information.
Now, I have some classes that implement the same interface (polymorphism) and have to work with them during a request. This means I also expose them to the REST API and can be included in the request/response body. Here is the interface:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "service",
defaultImpl = DefaultNotificatorPresentation.class,
visible = true
)
#JsonSubTypes({
#JsonSubTypes.Type(value = EmailNotificatorPresentation.class, name = "email")
})
public interface NotificatorPresentation {
String getService();
}
Basically, there are different types of notificators which all have different service (email, sms, etc). This property is used for the #JsonTypeInfo. Everything works as expected until I started testing if JsonMappingException is thrown correctly with the JSON subtypes.
JsonMappingException is thrown for all properties (eg. when malformed) and InvalidTypeIdException when service is not any of the available types (only email at the moment). I would like to tell the user the available options for the service property (when string is given but does not match the available types - email, sms, etc) and that it is malformed when no string is provided (object or array for example).
I came up with a solution that uses defaultImpl of #JsonTypeInfo and uses a custom class with custom validation annotation and ConstraintValidator that handles it.
public class DefaultNotificatorPresentation implements NotificatorPresentation {
// implementation of getService() and validation annotation
}
The annotation has a default message - available services are only email, sms. That way, every time the default implementation is created (always when an invalid service is given by the user) there will be a validation error. This approach works when the property service in the json request is of type string - "not found service" for example.
But when object ({ "example": true }) is set to the service property, the defaultImpl class is created twice. The first instance is given property service "{" (the first character of { "example": true }). The second one service is just null. This creates 2 validation exceptions but must throw JsonMappingException.
Do you have any ideas on how this can be solved? I can even use a totally different approach that handles Jackson polymorphism.

Swagger Codegen 3 and Spring HATEOAS

I'm taking my first swing at creating a RESTful API with OAS 3.0, Swagger Codegen, Spring MVC, and Spring HATEOAS. I want to use the OAS definition for documentation generation and server/client stub generation and use HATEOAS to create hyperlinks related resources.
I currently have my resources extending ResourceSupport and can add my links such that the responses have the _embedded and _links fields that I would expect. My issue is how to properly map the HATEOAS Resource to the model generated by Swagger codegen. My OAS definition matches the hal+json response, so the fields are identical in the swagger model and my HATEOAS Response.
Is there a way to easily map these? I'm also willing to accept that I am interpreting this incorrectly and that these frameworks don't really mesh together.
OAS example:
responses:
200:
description: ...
content:
application/hal+json:
schema:
$ref: '#/components/schemas/OasPersonResponse'
components:
schemas:
OasPersonResponse:
type: object
properties:
firstName:
type: string
lastName:
type: string
_links:
type: object
properties:
self:
type: object
properties:
href:
type: string
Resource example:
public class PersonResource extends ResourceSupport {
private final Person person;
public PersonResource(Person person) {
this.person = person;
}
public String getFirstName() {
return person.getFirstName();
}
public String getLastName() {
return person.getLastName();
}
}
Controller Example:
#Controller
public class PersonController implements PersonApi {
#Override
public ResponseEntity<OasPersonResponse> getPersonById(Integer personId) {
Person person = someDb.getPerson(personId);
PersonResource personResource = new PersonResource(person);
personResource
.add(linkTo(methodOn(PersonController.class)
.getPersonById(personId))
.withSelfRel();
Resource<PersonResource> returnResource =
new Resource(personResource);
return new ResponseEntity<>(returnResponse, HttpStatus.OK);
}
My issue is with the stub generated by swagger codegen expecting a return type of ResponseEntity<OasPersonResponse> but have a reference to a Resource<PersonResource>. Both OasPersonResponse and PersonResource represent the same data but the OasPersonResponse explicitly defines the _links object whereas the response with the PersonResource gets serialized to have the _links object.
Is there an easy way for me to convert the HATEOAS Resource to the model that was created by swagger codegen?
Thanks in advance for the help and guidance.
I'm currently working on a very similar project!
Firstly, if you can, I'd recommend using the 1.0.0.RC1 version of spring-hateoas as it has some pretty major quality of life improvements over the 0.25.x release branch. Of major relevance is that using the EntityModel wrapper class is now the recommended practice, which means you can just leave the relations out of your base entity specification. (The downside is this reduces the immediate utility of the OpenAPI spec; I haven't quite figured out how to reconcile that yet.)
Secondly, I'm afraid there doesn't seem to be much existing work on the swagger-codegen side as far as supporting Spring HATEOAS is concerned; in fact, I keep running into annoying bugs with the plain Spring "language" generator.
So either we can write our own swagger-codegen generator for spring-hateoas, or just heavily customize some templates to get "close enough" (there's less of this needed when using the EntityModel wrapper rather than extending ResourceSupport). I've gone with the latter approach so far, for what that's worth.

Spring Boot serialize text/javascript to JSON

I created the following Kotlin data class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public data class ITunesArtist(val artistName: String,
val artistId: Long, val artistLinkUrl: URL)
(a data class is a Kotlin class that auto-generates equals, hashcode, toString etc at compile time - saves time).
Now I've tried populating it using Spring RestTemplate:
#Test
fun loadArtist()
{
val restTemplate = RestTemplate()
val artist = restTemplate.getForObject(
"https://itunes.apple.com/search?term=howlin+wolf&entity=allArtist&limit=1", ITunesQueryResults::class.java);
println("Got artist: $artist")
}
It fails with:
Could not extract response: no suitable HttpMessageConverter found for response type
[class vampr.api.service.authorization.facebook.ITunesArtist]
and content type [text/javascript;charset=utf-8]
Fair enough - the JSON object mapper was probably expecting mime-type of text/json. Other than telling RestTemplate to map to String::class.java, and then instantiating an instance of JacksonObjectMapper by hand, is there a way to tell my RestTemplate to treat the returned mime type as JSON?
Instead of providing defaults for all properties in your data class you can also use this: https://github.com/FasterXML/jackson-module-kotlin
This Jackson module will allow you to serialize and deserialize Kotlin's data classes without having to worry about providing an empty constructor.
In a Spring Boot Application you can register the module with a #Configuration class like so:
#Configuration
class KotlinModuleConfiguration {
#Bean
fun kotlinModule(): KotlinModule {
return KotlinModule()
}
}
Other than that you can also use the extension functions mentioned in the documentation to register the module with Jackson.
Besides supporting data classes you will also get support for several classes from the Kotlin stdlib, like Pair for example.
Not sure about Spring, but Jackson needed me to specify that I worked with a Java Bean. You see, Kotlin data class is exactly the same as a standard Bean on the byte code level.
Do not forget that Java Bean specification implies an empty constructor (without parameters). A nice way to have it auto-generated is to provide default values for all parameters of your primary constructor.
To serialize an object from Jackson to String:
The 'get' portion of Java Beans specification is required.
To read a JSON string to object:
The 'set' portion of the spec is required.
Additionally the object requires an empty constructor.
Modify the class to include:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
data public class ITunesArtist(var artistName: String? = null,
var artistId: Long = -1L, val amgArtistId: String = "id",
var artistLinkUrl: URL? = null)
Fields provide defaults in order for there to be an empty constructor.
Edit:
Uses the Kotlin Module from #mhlz's (now accepted) answer removes the need to provide a default constructor.

Use Json root element only for some classes

I'm using dropwizard to create REST API. But I dont understand, how can I configure Jackson to exclude some classes from WRAP_ROOT_VALUE/UNWRAP_ROOT_VALUE features? Right now I get a post request with json body that doesn't include root element name:
{
"identification": "dummyuser",
"password":"dummypass"
}
This should map to java class LoginRequest:
public class LoginRequest {
public String identidication;
public String passwrd;
}
I also get requests for some types that include root element name:
{
"user":{
"id":12345,
"name":"John Doe"
}
}
This should be mapped to:
#JsonRootName("user")
public class User {
...
}
To get root element working I had to include:
environment.getObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true);
environment.getObjectMapper().configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
but now it applies for all classes. This means that whenever login request comes in, server will throw an error because it expects to see root element name.
Use JsonTypeName with JsonTypeInfo instead of JsonRootName:
#JsonTypeName("user")
#JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use= JsonTypeInfo.Id.NAME)
public class User {
...
}
#JsonTypeName
Annotation used for binding logical name that the annotated class has. Used with JsonTypeInfo (and specifically its JsonTypeInfo.use() property) to establish relationship between type names and types.
#JsonTypeInfo
Annotation used for configuring details of if and how type information is used with JSON serialization and deserialization, to preserve information about actual class of Object instances. This is necessarily for polymorphic types, and may also be needed to link abstract declared types and matching concrete implementation.

Categories