entities.BakuEntityResolvingJSONEncoder.kt Maven / Gradle / Ivy
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)!!
}
}