Adding Jackson KotlinModule to Micronaut JMS - kotlin

I created a JMSListener which listens an AWS SQS queue. I receive message successfully but I cannot deserialize received message to a class without default values.
This is my queue listener function:
#Queue(value = "queuename", concurrency = "1-10", acknowledgeMode = JMSContext.CLIENT_ACKNOWLEDGE)
fun receive(#MessageBody sqsMessage: SQSMessageDto) {
....
}
...and class:
class SQSMessageDto(
val notificationType: String,
val mail: Mail,
val receipt: Receipt
)
Function cannot parse deserialize text to SQSMessageDto unless I give default values for fields.
What I tried?
It works when I add a breakpoint in debugger mode on io.micronaut.jms.serdes.DefaultSerializerDeserializer and register Jackson's KotlinModule manually. (OBJECT_MAPPER.registerModule(new KotlinModule())) But I don't know how to make it properly.
Error Message:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.example.sqs.SQSMessageDto (no Creators,
like default constructor, exist): cannot deserialize from Object value
(no delegate- or property-based Creator) at [Source:
(String)"{.......}"]

just use a BeanCreatedEventListener<ObjectMapper>
like shown here: https://stackoverflow.com/a/53195504/7776688
#Singleton
static class ObjectMapperBeanEventListener implements BeanCreatedEventListener<ObjectMapper> {
#Override
public ObjectMapper onCreated(BeanCreatedEvent<ObjectMapper> event) {
final ObjectMapper mapper = event.getBean();
mapper.registerModule(new KotlinModule())
return mapper;
}
}

Related

Use #ConfigurationProperties in Annotation parameter/ argument? (must be compile-time constant)

I´m trying to use a Configuration in an Annotation, like so:
#ConfigurationProperties("scheduling")
interface SchedulingConfiguration {
val initialDelay: String
val fixedDelay: String
}
#Singleton
class Worker(
private val configuration: SchedulingConfiguration,
) {
private val log = LoggerFactory.getLogger(javaClass)
#Scheduled(initialDelay = configuration.initialDelay, fixedDelay = configuration.fixedDelay)
fun fetchQueueEntry() {
log.info("Fetching entry")
}
}
I´m getting the warning An annotation argument must be a compile-time constant.
Is there some way to get this working with Micronaut?
I managed to get it running by browsing the Micronaut documentation and accidentally stumbling across Property Placeholders. This will work fine, even though not feeling 'optimal'.
#Singleton
class Worker {
private val log = LoggerFactory.getLogger(javaClass)
#Scheduled(
initialDelay = "\${scheduling.initialDelay}",
fixedDelay = "\${scheduling.fixedDelay}"
)
fun fetchQueueEntry() {
log.info("Fetching entry")
}
}
It´s also possible to define default values that will be used if the keys are not present in config files or env variables:
#Scheduled(
initialDelay = "\${scheduling.initialDelay:0s}",
fixedDelay = "\${scheduling.fixedDelay:10s}"
)
With no default values and absent configuration for used property placeholders, an Exception will be thrown at runtime and the application will shut down.

kotlinx.serialization: inject local parameter

I need to inject a local value to a class constructor during deserialization. For example, look at the following class.
#Serializable
class SomeClass(val local: Context, val serialized: String)
I want the field local to be skipped during serialization and substituted with some predefined local value during deserialization.
The reason behind is that I'm going to transfer models through network, but operations on these models rely on a local context which I want to inject.
Because I haven't find any standard ways to achieve it, I've decided to make use of contextual serialization. So I have written the serializer:
class ContextualInjectorSerializer<T>(private val localValue: T) : KSerializer<T> {
override val descriptor = SerialDescriptor("ValueInjection", StructureKind.OBJECT)
override fun deserialize(decoder: Decoder): T {
decoder.beginStructure(descriptor).endStructure(descriptor)
return localValue
}
override fun serialize(encoder: Encoder, value: T) {
encoder.beginStructure(descriptor).endStructure(descriptor)
}
}
And used it this way:
// Context is marked with #Serializable(with = ContextSerializer::class)
val json = Json(JsonConfiguration.Stable, SerializersModule {
contextual(Context::class, ContextualInjectorSerializer(context))
})
// serialize/deserialize
Surprisingly, it works pretty fine on JVM. However, when I compiled it to JS and tested, I got TypeError: Cannot read property 'siteId' of undefined. Here siteId is a field of Context which I try to access.
Is there a standard way to inject local parameters? What's wrong with my trick?

Jackson Deserialization with no Type

I have a question related to Jackson and polymorphism: is there a way to deserialize a JSON string without specifying a type?
Assuming I don't own this message (e.g., external API) and I have two separate messages that come in at separate times:
{
"responseCode": 200
"responseMessage": "You did something successfully"
}
{
"errorCode": 401
"errorDescription": "Permission denied"
}
And I want to deserialize this message with some data classes that I created based on these messages through polymorphism (see abstract class in next code block):
data class MyDataClass(
val responseCode: Int,
val responseMessage: String
): MyAbstractClass()
data class MyOtherDataClass(
val errorCode: Int,
val errorDescription: String
): MyAbstractClass()
And I am resolving these messages through a function that will use the Jackson Object Mapper to deserialize the stringified JSON payload:
#JsonSubTypes(
JsonSubTypes.Type(value = MyDataClass::class),
JsonSubTypes.Type(value = MyOtherDataClass::class)
)
#JsonIgnoreProperties(ignoreProperties = true)
abstract class MyAbstractClass
fun receiveMessage(message: String) {
val convertedMessage = jacksonObjectMapper().readValue<MyAbstractClass>(message)
log.info(convertedMessage)
/* prints either:
MyDataClass(responseCode=200, responseMessage=You did something successfully)
OR
MyOtherDataClass(errorCode=401, errorDescription=Permission denied)
*/
}
But since I haven't described how to identify the data class (using #JsonTypeInfo), it fails.
To repeat, I am curious if there is a way that I can deserialize the incoming message to one of my polymorphic types without having to specify the #JsonTypeInfo. Or if I must describe the #JsonTypeInfo, how would I do this with no similarities between the two child classes of MyAbstractClass?
I would write a custom deserializer which takes it as a generic JSONObject or the like. Then I'd check if a differentiating key exists. For example:
// pseudocode
when (json: JSONObject) {
hasKey("responseCode") -> // deserialize as MyDataClass
hasKey("errorCode") -> // deserialize as MyOtherDataClass
}

GSON Deserialization of subtypes in Kotlin

I'm not sure if this is a limitation, a bug or just bad use of GSON. I need to have a hierarchy of Kotlin objects (parent with various subtypes) and I need to deserialize them with GSON. The deserialized object has correct subtype but its field enumField is actually null.
First I thought this is because the field is passed to the "super" constructor but then I found out that "super" works well for string, just enum is broken.
See this example:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory
open class Parent(val stringField: String,
val enumField: EnumField) {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2)
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type)
val subtypeRAF = RuntimeTypeAdapterFactory.of(Parent::class.java, "enumField")
.registerSubtype(Subtype1::class.java, Parent.EnumField.SUBTYPE1.name)
.registerSubtype(Subtype2::class.java, Parent.EnumField.SUBTYPE2.name)
.registerSubtype(Subtype3::class.java, Parent.EnumField.SUBTYPE3.name)
fun main() {
val gson = GsonBuilder()
.registerTypeAdapterFactory(subtypeRAF)
.create()
serializeAndDeserialize(gson, Subtype1()) // this works (but not suitable)
serializeAndDeserialize(gson, Subtype2("s2")) // broken
serializeAndDeserialize(gson, Subtype3("s3", Parent.EnumField.SUBTYPE3)) // broken
}
private fun serializeAndDeserialize(gson: Gson, obj: Parent) {
println("-----------------------------------------")
val json = gson.toJson(obj)
println(json)
val obj = gson.fromJson(json, Parent::class.java)
println("stringField=${obj.stringField}, enumField=${obj.enumField}")
}
Any ideas how to achieve to deserialization of enumField?
(deps: com.google.code.gson:gson:2.8.5, org.danilopianini:gson-extras:0.2.1)
P.S.: Note that I have to use RuntimeAdapterFactory because I have subtypes with different set of fields (I did not do it in the example so it is easier to understand).
Gson requires constructors without arguments to work properly (see deep-dive into Gson code below). Gson constructs raw objects and then use reflection to populate fields with values.
So if you just add some argument-less dummy constructors to your classes that miss them, like this:
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2) {
constructor() : this("")
}
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type) {
constructor() : this("", EnumField.SUBTYPE3)
}
you will get the expected output:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
Gson deep-dive
If you want to investigate the internals of Gson, a tip is to add an init { } block to Subtype1 since it works and then set a breakpoint there. After it is hit you can move up the call stack, step through code, set more breakpoints etc, to reveal the details of how Gson constructs objects.
By using this method, you can find the Gson internal class com.google.gson.internal.ConstructorConstructor and its method newDefaultConstructor(Class<? super T>) that has code like this (I have simplified for brevity):
final Constructor<? super T> constructor = rawType.getDeclaredConstructor(); // rawType is e.g. 'class Subtype3'
Object[] args = null;
return (T) constructor.newInstance(args);
i.e. it tries to construct an object via a constructor without arguments. In your case for Subtype2 and Subtype3, the code will result in a caught exception:
} catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
return null; // set breakpoint here to see
}
i.e. your original code fails since Gson can't find constructors without arguments for Subtype2 and Subtype3.
In simple cases, the problem with missing argument-less constructors is worked around with the newUnsafeAllocator(Type, final Class<? super T>)-method in ConstructorConstructor, but with RuntimeTypeAdapterFactory that does not work correctly.
I may be missing something in what you're trying to achieve, but is it necessary to use the RuntimeTypeAdapterFactory? If we take out the line where we register that in the Gson builder, so that it reads
val gson = GsonBuilder()
.create()
Then the output returns the enum we would expect, which looks to be serialising / deserialising correctly. I.e. the output is:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
It also may be an idea to implement Serializable in Parent. i.e.
open class Parent(val stringField: String, val enumField: EnumField) : Serializable {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
Try adding #SerializedName annotation to each enum.
enum class EnumField {
#SerializedName("subtype1")
SUBTYPE1,
#SerializedName("subtype2")
SUBTYPE2,
#SerializedName("subtype3")
SUBTYPE3
}

Problem deserialize flux/mono into Feign Spring Cloud

i develop a micro services application with Kotlin Webflux (Reactor3), Eureka, Zuul and Feign. Except that I always have an error when I make a call to an API via my micro service Feign. It looks like he can not deserialize the data. Could you please tell me if Feign is compatible with Flux and Monno?
thank you
{
"timestamp": "2019-05-29T07:39:43.998+0000",
"path": "/hobbies/",
"status": 500,
"error": "Internal Server Error",
"message": "Type definition error: [simple type, class reactor.core.publisher.Flux]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of reactor.core.publisher.Flux (no Creators, like default construct, 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]"
}
Feign doesn't provide support for Mono/Flux deserialiazation. There exists alternative feign library who fully support it: feign-reactive.
Note though, this is a rewrite of feign which fully use reactive code, differ from OpenFeign's Feign core.
Here's a snippet on how to use it, alongside with normal Feign, taken from the sample app.
#SpringBootApplication(exclude = ReactiveLoadBalancerAutoConfiguration.class)
#RestController
#EnableReactiveFeignClients
#EnableFeignClients
public class FeignApplication {
#Autowired
private GreetingReactive reactiveFeignClient;
#Autowired
private Greeting feignClient;
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
#GetMapping("/greetingReactive")
public Mono<String> greetingReactive() {
return reactiveFeignClient.greeting().map(s -> "reactive feign! : " + s);
}
#GetMapping("/greeting")
public String greeting() {
return "feign! : " + feignClient.greeting();
}
}
In addition to Adhika Setya Pramudita response, I would like to mention that in order to return Mono in controller, you must use Spring WebFlux instead of Spring MVC
I was not able to make #Adhika Setya Pramudita solution working and gut tells me that it cannot even run due to mixing #EnableReactiveFeignClients and
#EnableFeignClients which are require corresponding #EnableWebFlux or #EnableWebMvc and thus defining both may compile but will fail in runtime.
Since op did not mention target language I'm feeling like to share Kotlin setup that works in my case:
build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("com.playtika.reactivefeign:feign-reactor-core:2.0.22")
implementation("com.playtika.reactivefeign:feign-reactor-spring-configuration:2.0.22")
implementation("com.playtika.reactivefeign:feign-reactor-webclient:2.0.22")
Config.kt
#Configuration
#EnableWebFlux
#EnableReactiveFeignClients
class Config {
}
MyEntity.kt
class MyEntity #JsonCreator constructor(
#param:JsonProperty("my_value") val my_value: String
)
MyFeignClient.kt
#Component
#ReactiveFeignClient(
url = "\${package.service.my-service-url}",
name = "client"
)
interface MyFeignClient {
#GetMapping(value = ["/my/url?my_param={my_value}"], consumes = ["application/json"])
fun getValues(
#PathVariable(name = "my_value") myValue: String?,
): Mono<MyEntity?>?
}
Then here goes code in some service class:
val myClient: MyFeignClient = WebReactiveFeign.builder<MyFeignClient>()
.contract(ReactiveContract(SpringMvcContract()))
.target(MyFeignClient::class.java, "http://example.com")
// feel free to add .block() to get unpacked value or just chain your logic further
val response = myClient.getValues(param)