All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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)!!
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy