entities.BakuEntityResolvingJSONEncoder.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of baku Show documentation
Show all versions of baku Show documentation
helps you focus your REST API back-end on the business logic
package com.github.fluidsonic.baku
import com.github.fluidsonic.fluid.json.*
import kotlinx.coroutines.channels.associateByTo
import org.slf4j.LoggerFactory
import java.io.Writer
internal class BakuEntityResolvingJSONEncoder(
private val codecProvider: JSONCodecProvider,
override val context: Transaction,
private val entityResolver: EntityResolver,
writer: Writer
) : JSONEncoder, JSONWriter by JSONWriter.build(writer) {
private val cachedEntities: MutableMap = hashMapOf()
private val entityReferences: MutableSet = hashSetOf()
suspend fun writeEntities() {
writeMapStart()
val resolvedIds = hashSetOf()
val unresolvedIds = mutableListOf()
var runCount = 0
var idsToResolve: Set = entityReferences
while (idsToResolve.isNotEmpty()) {
runCount += 1
resolvedIds += idsToResolve
val resolvedEntitiesById = entityResolver
.resolve(ids = idsToResolve, transaction = context)
.associateByTo(hashMapOf()) { it.id }
for (id in idsToResolve) {
if (!resolvedEntitiesById.containsKey(id)) {
val cachedEntity = cachedEntities[id]
if (cachedEntity != null) {
resolvedEntitiesById[id] = cachedEntity
}
else {
unresolvedIds.add(id)
}
}
}
for (entity in resolvedEntitiesById.values) {
writeValue(entity.id, collect = false)
writeValue(entity)
}
idsToResolve = entityReferences - resolvedIds
}
if (unresolvedIds.isNotEmpty()) {
log.warn("Response references entities which cannot be found: " + unresolvedIds.joinToString(", "))
}
if (runCount >= 4) {
log.debug("Response serialization took $runCount runs! Consider flattening the entity hierarchy.")
}
writeMapEnd()
}
@Suppress("UNCHECKED_CAST")
private fun writeValue(value: Any, collect: Boolean) {
withErrorChecking {
if (collect && value is EntityId) {
entityReferences += value
}
(codecProvider.encoderCodecForClass(value::class) as JSONEncoderCodec?)
?.run {
try {
isolateValueWrite {
encode(value = value)
}
}
catch (e: JSONException) {
// TODO remove .java once KT-28418 is fixed
e.addSuppressed(JSONException.Serialization("… when encoding value of ${value::class} using ${this::class.java.name}: $value"))
throw e
}
}
?: throw JSONException.Serialization(
message = "No encoder codec registered for ${value::class}: $value",
path = path
)
}
}
override fun writeValue(value: Any) =
writeValue(value, collect = true)
companion object {
private val log = LoggerFactory.getLogger(BakuEntityResolvingJSONEncoder::class.java)!!
}
}