Using Ktor, Kmongo and kotlinx.serialization together causing ClassCastException ... What am I doing wrong?

1.1k Views Asked by At

https://github.com/reticent-monolith/winds_server is the github repo if anyone finds it easier looking there.

I'm trying to use KMongo and Ktor with Kotlin's Serialization module but creating the MongoClient results in the following exception:

java.lang.ClassCastException: class org.litote.kmongo.serialization.SerializationCodecRegistry cannot be cast to class org.bson.codecs.configuration.CodecProvider (org.litote.kmongo.serialization.SerializationCodecRegistry and org.bson.codecs.configuration.CodecProvider are in unnamed module of loader 'app')
    at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry(ClassMappingTypeService.kt:83)
    at org.litote.kmongo.serialization.SerializationClassMappingTypeService.codecRegistry(SerializationClassMappingTypeService.kt:40)
    at org.litote.kmongo.serialization.SerializationClassMappingTypeService.coreCodecRegistry(SerializationClassMappingTypeService.kt:97)
    at org.litote.kmongo.service.ClassMappingType.coreCodecRegistry(ClassMappingType.kt)
    at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry$default(ClassMappingTypeService.kt:79)
    at org.litote.kmongo.KMongo.configureRegistry$kmongo_core(KMongo.kt:79)
    at org.litote.kmongo.KMongo.createClient(KMongo.kt:70)
    at org.litote.kmongo.KMongo.createClient(KMongo.kt:57)
    at org.litote.kmongo.KMongo.createClient(KMongo.kt:47)
    at org.litote.kmongo.KMongo.createClient(KMongo.kt:39)
    at com.reticentmonolith.repo.MongoDispatchRepo.<init>(MongoDispatchRepo.kt:11)
    at com.reticentmonolith.ApplicationKt.<clinit>(Application.kt:14)
    ... 23 more

Am I missing something in my build.gradle?

buildscript {
    repositories {
        mavenCentral()
    }
    
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
    }
}

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.5.10'
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10'
    id 'application'
}

group 'com.reticentmonolith'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
//    KOTLIN
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

//    KTOR
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    implementation "ch.qos.logback:logback-classic:$logback_version"
    implementation "io.ktor:ktor-server-core:$ktor_version"
    testImplementation "io.ktor:ktor-server-tests:$ktor_version"
    implementation "io.ktor:ktor-serialization:$ktor_version"

//    MONGO
    implementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.8'

//    KOTLINX SERIALIZATION
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"

//    KMONGO
    implementation 'org.litote.kmongo:kmongo-serialization:4.2.7'
    implementation group: 'org.litote.kmongo', name: 'kmongo-id-serialization', version: '4.2.7'

//    KBSON (optional dependency for KMONGO and Serialization)
    implementation "com.github.jershell:kbson:0.4.4"

}

Here's my MongoClient class:

import com.mongodb.client.MongoDatabase
import com.reticentmonolith.models.Dispatch
import java.time.LocalDate

import org.litote.kmongo.*

class MongoDispatchRepo: DispatchRepoInterface {

    private val client = KMongo.createClient()
    private val database: MongoDatabase = client.getDatabase("zw")
    private val windsData = database.getCollection<Dispatch>("winds")


    override fun createDispatch(dispatch: Dispatch) {
        windsData.insertOne(dispatch)
    }

    override fun getAllDispatches(): Map<String, List<Dispatch>> {
        val list = windsData.find().toList()
        return mapOf("dispatches" to list)
    }

    override fun getDispatchesByDate(date: LocalDate): Map<String, List<Dispatch>> {
        return mapOf("dispatches" to windsData.find(Dispatch::date eq date).toList())
    }

    override fun getDispatchesByDateRange(start: LocalDate, end: LocalDate): Map<String, List<Dispatch>> {
        return mapOf("dispatches" to windsData.find(Dispatch::date gte(start), Dispatch::date lte(end)).toList())
    }

    override fun getDispatchById(id: Id<Dispatch>): Dispatch? {
        return windsData.findOneById(id)
    }

    override fun updateDispatchById(id: Id<Dispatch>, update: Dispatch): Boolean {
        val oldDispatch = getDispatchById(id)
        if (oldDispatch != null) {
            update.date = oldDispatch.date
            update._id = oldDispatch._id
            windsData.updateOneById(id, update)
            return true
        }
        return false
    }

    override fun updateLastDispatch(update: Dispatch): Boolean {
        val lastDispatch = getLastDispatch() ?: return false
        update.date = lastDispatch.date
        update._id = lastDispatch._id
        windsData.updateOneById(lastDispatch._id, update)
        return true
    }

    override fun deleteDispatchById(id: Id<Dispatch>) {
        windsData.deleteOne(Dispatch::_id eq id)
    }

    override fun getLastDispatch(): Dispatch? {
        val todaysDispatches = getDispatchesByDate(LocalDate.now())
        if (todaysDispatches.isEmpty()) return null
        return todaysDispatches["dispatches"]?.last()
    }

    override fun addSpeedsToLastDispatch(line4: Int?, line3: Int?, line2: Int?, line1: Int?) {
        val lastDispatch = getLastDispatch() ?: return
        lastDispatch.riders.get(4)?.speed = line4
        lastDispatch.riders.get(3)?.speed = line3
        lastDispatch.riders.get(2)?.speed = line2
        lastDispatch.riders.get(1)?.speed = line1
        updateLastDispatch(lastDispatch)
    }

    fun purgeDatabase() {
        windsData.deleteMany(Dispatch::comment eq "")
    }
}

And my Application.kt for Ktor:

package com.reticentmonolith

import com.reticentmonolith.models.createExampleDispatches
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.http.*
import com.reticentmonolith.repo.MongoDispatchRepo
import io.ktor.features.*
import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule

val repo = MongoDispatchRepo()

fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {

    install(ContentNegotiation) {
        json(
            Json { serializersModule = IdKotlinXSerializationModule  },
            contentType = ContentType.Application.Json
        )
    }

    repo.purgeDatabase()
    val dispatchList = repo.getAllDispatches()["dispatches"]
    if (dispatchList != null && dispatchList.isEmpty()) {
        createExampleDispatches(0).forEach {
            repo.createDispatch(it)
            println("######## date: ${it.date} ################")
        }
    }

    routing {
        get("/dispatches/") {
            call.response.status(HttpStatusCode.OK)
            val dispatches = repo.getAllDispatches()
            println("Dispatches from Mongo: $dispatches")
            println("Dispatches encoded for response: $dispatches")
        }
    }
}


And finally the class being serialized:

package com.reticentmonolith.models

import com.reticentmonolith.serializers.DateSerializer
import com.reticentmonolith.serializers.TimeSerializer
import kotlinx.serialization.Contextual
import org.litote.kmongo.*
import kotlinx.serialization.Serializable
import java.time.LocalDate
import java.time.LocalTime

@Serializable
data class Dispatch(
    var riders: MutableMap<Int, @Contextual Rider?> = mutableMapOf(
        1 to null,
        2 to null,
        3 to null,
        4 to null
    ),
    var comment: String = "",
    var wind_degrees: Int,
    var wind_speed: Double,
    var winds_instructor: String,
    var bt_radio: String,
    var _id: @Contextual Id<Dispatch> = newId(),
//    @Serializable(with=DateSerializer::class)
    @Contextual var date: LocalDate = LocalDate.now(),
//    @Serializable(with=TimeSerializer::class)
    @Contextual var time: LocalTime = LocalTime.now()
)

fun createExampleDispatches(amount: Int): Collection<Dispatch> {
    val dispatches = mutableListOf<Dispatch>()
    for (i in 0..amount) {
        dispatches.add(Dispatch(
            bt_radio = "BT Instructor",
            winds_instructor = "Winds Instructor",
            wind_speed = 20.5,
            wind_degrees = 186
        ).apply {
            this.riders[2] = Rider(67, front_slider = Slider.BLACK, trolley = 125)
            this.riders[3] = Rider(112, front_slider = Slider.NEW_RED, rear_slider = Slider.YELLOW, trolley = 34)
        })
    }
    return dispatches
}

Any help would be hugely appreciated :) Thanks!

0

There are 0 best solutions below