Javalin Migration - kotlin

I am new to Kotlin and Javalin. While migrating from Javalin 3 to 4, Javalinjackson.configure() function is deprecated. Below is the part of the code
import io.javalin.plugin.json.JavalinJackson
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
val om = jacksonObjectMapper().apply { registerModule(JavaTimeModule()) }
JavalinJackson.configure(om)
I read in the documentation is that config.jsonMapper() is used now. Any pointers would be helpful.

JavalinJackson is not a singleton any more. To "configure" it just pass your ObjectMapper as a constructor parameter:
val om = jacksonObjectMapper().apply { registerModule(JavaTimeModule()) }
val jacksonMapper = JavalinJackson(om)
and then pass resulting instance of JsonMapper into Javalin config:
val app = Javalin.create { config: JavalinConfig ->
config.jsonMapper(jacksonMapper)
}.start()

Related

Serialize generic class using kotlinix.serialization in Kotlin/JS fails

Serializing a generic class with kotlinx.serialization succeeds in JVM but fails in JavaScript with message TypeError: tmp$.serializer is not a function. Please, see the following Unit Test.
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlin.test.Test
#Serializable
data class SerializationTest<T>(val myValue: T)
#Serializable
data class A(val st : SerializationTest<Int>)
class SerializationTests {
#Test
fun serializeWithKotlinx() {
// Succeeds in JVM but throws in JavaScript with "TypeError: tmp$.serializer is not a function"
Json.encodeToString(SerializationTest(3))
}
#Test
fun serializeWithKotlinxWithBox() {
// Succeeds always
Json.encodeToString(A(SerializationTest(3)))
}
}
How can I serialize a generic class in JavaScript?
See the docs here
Please note that this example works only on JVM because of serializer function restrictions. For JS and Native, explicit serializer should be used: format.encodeToString(PolymorphicSerializer(Project::class), data) You can keep track of this issue here.
In your case, this code:
Json.encodeToString(SerializationTest(3))`
uses generics, which is only available on JVM.
You'll have to manually pass the serializer to encodeToString(...)
#Test
fun serializeWithKotlinx() {
val encoded =
Json.encodeToString(
SerializationTest.serializer(Int.serializer()),
SerializationTest(3),
)
println(encoded)
}
Or use a SerializersModule (documented here):
#Test
fun serializeWithKotlinxSerializersModule() {
val module = SerializersModule {
contextual(SerializationTest::class) { args ->
SerializationTest.serializer(args[0])
}
}
val mapper = Json { serializersModule = module }
val encoded = mapper.encodeToString(
SerializationTest(3),
)
println(encoded)
}
As per this GitHub issue:
Unfortunately, this is a known problem and we do not support JS legacy anymore. Please use IR backend if possible
With JS IR it works well, giving the same results as JVM.

Micronaut-Core: How to create dynamic endpoints

Simple question. Is it possible to create endpoints without #Endpoint?
I want to create rather dynamic endpoints by a file and depending on the content to its context.
Thanks!
Update about my idea. I would to create something like a plugin system, to make my application more extensible for maintenance and future features.
It is worth to be mentioned I am using Micronaut with Kotlin. Right now I've got fixed defined Endpoints, which matches my command scripts.
My description files will be under /src/main/resources
I've got following example description file how it might look like.
ENDPOINT: GET /myapi/customendpoint/version
COMMAND: """
#!/usr/bin/env bash
# This will be executed via SSH and streamed to stdout for further handling
echo "1.0.0"
"""
# This is a template JSON which will generate a JSON as production on the endpoint
OUTPUT: """
{
"version": "Server version: $RESULT"
}
"""
How I would like to make it work with the application.
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener
#Singleton
class SampleEventListener {
/*var invocationCounter = 0
#EventListener
internal fun onSampleEvent(event: SampleEvent) {
invocationCounter++
}*/
#EventListener
internal fun onStartupEvent(event: StartupEvent) {
// 1. I read all my description files
// 2. Parse them (for what I created a parser)
// 3. Now the tricky part, how to add those information to Micronaut Runtime
val do = MyDescription() // After I parsed
// Would be awesome if it is that simple! :)
Micronaut.addEndpoint(
do.getEndpoint(), do.getHttpOption(),
MyCustomRequestHandler(do.getCommand()) // Maybe there is a base class for inheritance?
)
}
#EventListener
internal fun onShutdownEvent(event: ShutdownEvent) {
// shutdown logic here
}
}
You can create a custom RouteBuilder that will register your custom endpoints at runtime:
#Singleton
class CustomRouteBuilder extends DefaultRouteBuilder {
#PostConstruct
fun initRoutes() {
val do = MyDescription();
val method = do.getMethod();
val routeUri = do.getEndpoint();
val routeHandle = MethodExecutionHandle<Object, Object>() {
// implement the 'MethodExecutionHandle' in a suitable manner to invoke the 'do.getCommand()'
};
buildRoute(HttpMethod.parse(method), routeUri, routeHandle);
}
}
Note that while this would still feasible, it would be better to consider another extension path as the solution defeats the whole Micronaut philosophy of being an AOT compilation framework.
It was actually pretty easy. The solution for me was to implement a HttpServerFilter.
#Filter("/api/sws/custom/**")
class SwsRouteFilter(
private val swsService: SwsService
): HttpServerFilter {
override fun doFilter(request: HttpRequest<*>?, chain: ServerFilterChain?): Publisher<MutableHttpResponse<*>> {
return Flux.from(Mono.fromCallable {
runBlocking {
swsService.execute(request)
}
}.subscribeOn(Schedulers.boundedElastic()).flux())
}
}
And the service can process with the HttpRequest object:
suspend fun execute(request: HttpRequest<*>?): MutableHttpResponse<Feedback> {
val path = request!!.path.split("/api/sws/custom")[1]
val httpMethod = request.method
val parameters: Map<String, List<String>> = request.parameters.asMap()
// TODO: Handle request body
// and do your stuff ...
}

What is the reason for an UninitializedPropertyAccessException in #SpringBootTest with MockK and Kotlin?

I am trying to use MockK 1.10.2 with Kotlin 1.4.10 and #SpringBootTest (Spring Boot 2.2.2.RELEASE) and I can't get it run because of a
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2020-11-05 15:00:37.878 WARN --- [ main] i.m.p.j.t.InliningClassTransformer : Failed to transform class java/lang/Object
java.lang.IllegalArgumentException: Unsupported class file major version 59
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:195)
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:176)
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:162)
at net.bytebuddy.utility.OpenedClassReader.of(OpenedClassReader.java:86)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3394)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1933)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:217)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:120)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3404)
at io.mockk.proxy.jvm.transformation.InliningClassTransformer.transform(InliningClassTransformer.kt:77)
at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
at io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation.retransform(JvmInlineInstrumentation.kt:28)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation$execute$1.invoke(RetransformInlineInstrumnetation.kt:19)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation$execute$1.invoke(RetransformInlineInstrumnetation.kt:6)
at io.mockk.proxy.common.transformation.ClassTransformationSpecMap.applyTransformation(ClassTransformationSpecMap.kt:41)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation.execute(RetransformInlineInstrumnetation.kt:16)
at io.mockk.proxy.jvm.ProxyMaker.inline(ProxyMaker.kt:88)
at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:30)
kotlin.UninitializedPropertyAccessException: lateinit property taService has not been initialized
exception. (The same happens if I use tmpService. It doesn't matter if the imported service is in the same or a different package as the test class.)
As you can see from the code of my test class I already played around a lot but without any success so far.
package com.xxx.emr.tm
import com.xxx.data.emr.model.PatientAssignment
import com.xxx.data.tm.TMPService
import com.xxx.data.tm.model.MyType
import com.xxx.data.tm.model.MyTypeCode
import com.xxx.data.tm.model.UserAction
import com.xxx.emr.service.model.AuthenticatedUser
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.SpyK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import org.junit.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
#ExtendWith(SpringExtension::class, MockKExtension::class)
#SpringBootTest // (classes = [com.xxx.emr.service.MyServiceSpringBoot::class])
// #RunWith(SpringJUnit4ClassRunner::class)
// #ActiveProfiles("test")
// #EnableAutoConfiguration
// #AutoConfigureMockMvc
class MyTest {
// (
// val tmpService: TMPService,
// val taService: TaService
// )
// #InjectMockKs
// #Autowired
// #SpyK
#MockK
private lateinit var tmpService: TMPService
#InjectMockKs
#Autowired
private lateinit var taService: TaService
#Test
fun assignAToB() {
MockKAnnotations.init(this, relaxUnitFun = true)
val vId = "xxx"
val authUser: AuthenticatedUser = AuthenticatedUser.mockedUser()
val userAction = UserAction(
userId = "user_id",
actionCode = ActionType.GET_IT,
sessionId = "sessionId"
)
val xxx = MyType(
MyTypeCode.ABC.value,
MyTypeCode.ABC.name,
)
val actionResult = UserActionResult(hashMapOf("success" to true))
// every { tmpService.getXxx(any()) } returns xxx
verify( exactly = 1 ) {
tmpService.doSomething(any(), any(), any())
}
verify( exactly = 1 ) {
taService.dowhatYouWant(„vId", authUser, userAction)
}
}
Autowiring does not work...so what is my fault? How can I make the test run at all?
I think you should initialize your mocks first, by adding this in your test file for example:
#org.springframework.test.context.event.annotation.BeforeTestMethod
fun initMocks() {
org.mockito.MockitoAnnotations.initMocks(this)
}
I created a post on another thread about how to setup unit testing, comparable with your requirements:
https://stackoverflow.com/a/64669499/7919904

Why serializing collections of different element types is not supported in ktor-serialization?

I am trying to make a simple server which gives serialized List in JSON. The List to be serialized is the example in the official blog post's Polymorphic serialization section.
But with the ktor's serialization feature, I get the following exception.
21:53:25.536 [nioEventLoopGroup-4-1] ERROR ktor.application - Unhandled: GET - /
java.lang.IllegalStateException: Serializing collections of different element types is not yet supported. Selected serializers: [DirectMessage, BroadcastMessage]
at io.ktor.serialization.SerializerLookupKt.elementSerializer(SerializerLookup.kt:71)
Since sealed class is a key feature to choose Kotlin, I really wonder why this is not supported.
Are there any good reasons for ktor-serialization not supporting this? Or should I post an issue for removing this check from SerializerLookup.kt?
I made this code by choosing New Project > Kotlin > Application in IntelliJ. The modified code is shown below.
My server.kt:
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.serialization.Serializable
#Serializable
sealed class Message {
abstract val content: String
}
#Serializable
data class BroadcastMessage(override val content: String) : Message()
#Serializable
data class DirectMessage(override val content: String, val recipient: String) : Message()
val data: List<Message> = listOf(
DirectMessage("Hey, Joe!", "Joe"),
BroadcastMessage("Hey, all!")
)
fun main() {
embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
install(ContentNegotiation) {
json()
}
routing {
get("/") {
call.respond(data)
}
}
}.start(wait = true)
}
My build.gradle.kts:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.10"
application
kotlin("plugin.serialization") version "1.4.10"
}
group = "com.example.ktor.serialization"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
jcenter()
maven {
url = uri("https://dl.bintray.com/kotlin/ktor")
}
maven {
url = uri("https://dl.bintray.com/kotlin/kotlinx")
}
}
dependencies {
testImplementation(kotlin("test-junit5"))
implementation("io.ktor:ktor-server-netty:1.4.1")
implementation("io.ktor:ktor-html-builder:1.4.1")
implementation("io.ktor:ktor-serialization:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
implementation("ch.qos.logback:logback-classic:1.2.3")
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
}
application {
mainClassName = "ServerKt"
}
As said in the stacktrace this is not supported yet., so it might come someday.
However, a workaround is still possible for such a case.
The issue is from Ktor, not Kotlinx Serialization.
So, you can serialize your data as JSON, and then send them as a response, like here:
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) { json() }
routing {
get("/") {
val data: List<Message> = listOf(
DirectMessage("Hey, Joe!", "Joe"),
BroadcastMessage("Hey, all!")
)
val string = Json.encodeToString(data)
call.respondText(string, contentType = ContentType.Application.Json)
}
}
}
#Serializable
sealed class Message {
abstract val content: String
}
#Serializable
data class BroadcastMessage(override val content: String) : Message()
#Serializable
data class DirectMessage(override val content: String, val recipient: String) : Message()
The reason is that we don't have the particular type information and can only analyze instance classes in runtime. Analyzing and runtime type intersection is not an easy task and for sure it will be very inefficient that is unacceptable on server-side.
Using typeOf could potentially help, but we haven't analyzed the performance impact of such a change (including allocation profile). The other reason is that we didn't know about typeOf (it didn't exist) and call.respond has been designed without it so this change will for sure be a breaking change.

Using data class in micronaut properties

I'm writing configuration properties and would like to use data class to hold the data.
Problem is, data classes have a primary constructor and are immutable, and micronaut tries to inject the values as beans.
Example:
#ConfigurationProperties("gerencianet")
data class GerenciaNetConfiguration(
val clientId: String,
val clientSecret: String,
val apiUrl: String,
val notificationUrl: String,
val datePattern: String = "yyyy-MM-dd HH:mm:ss"
)
Error: Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [java.lang.String] exists. Make sure the bean is not disabled by bean requirements
Is there support for it?
You can inject the values as constructor parameters using #Parameter. It avoids common mistakes with #Value.
For example, if your application.yml looks like this:
group:
first-value: asdf
second-value: ghjk
Then a Kotlin class might look like:
import io.micronaut.context.annotation.Property
import javax.inject.Singleton
#Singleton
class MyClass(#Property(name = "group.first-value") val firstValue: String) {
fun doIt(): String {
return firstValue
}
}
Or, similarly, a method:
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Property
import javax.inject.Singleton
#Factory
class MyFactory {
#Singleton
fun getSomeValue(#Property(name = "group.first-value") firstValue: String): SomeClass {
return SomeClass.newBuilder()
.setTheValue(firstValue)
.build()
}
}
One option you have is to do something like this:
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties
#ConfigurationProperties("my.engine")
internal class EngineConfig {
#ConfigurationBuilder(prefixes = ["with"])
val builder = EngineImpl.builder()
#ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "crank-shaft") / <3>
val crankShaft = CrankShaft.builder()
#set:ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "spark-plug")
var sparkPlug = SparkPlug.builder()
}
That is from our test suite at https://github.com/micronaut-projects/micronaut-core/blob/1c3e2c3280da200c96e629a4edb9df87875ef2ff/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/builder/EngineConfig.kt.
You can also inject the values as constructor parameters using #Value.
I hope that helps.