diff --git a/settings.gradle.kts b/settings.gradle.kts index 9d25625..896d04d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -52,5 +52,7 @@ include( ":controls-magix-client", ":motors", ":controls-xodus", - ":controls-mongo" + ":controls-mongo", + ":xodus-serialization" ) +include("xodus-serialization") diff --git a/xodus-serialization/build.gradle.kts b/xodus-serialization/build.gradle.kts new file mode 100644 index 0000000..dad94f5 --- /dev/null +++ b/xodus-serialization/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("ru.mipt.npm.gradle.jvm") + `maven-publish` +} + +val xodusVersion = "1.3.232" + +kscience { + useSerialization { + json() + } +} + +dependencies { + implementation(projects.magix.magixApi) + implementation(projects.controlsCore) + implementation("org.jetbrains.xodus:xodus-entity-store:$xodusVersion") + implementation("org.jetbrains.xodus:xodus-environment:$xodusVersion") + implementation("org.jetbrains.xodus:xodus-vfs:$xodusVersion") +} diff --git a/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/decoder.kt b/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/decoder.kt new file mode 100644 index 0000000..38e4a16 --- /dev/null +++ b/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/decoder.kt @@ -0,0 +1,42 @@ +package ru.mipt.npm.xodus.serialization.json + +import jetbrains.exodus.entitystore.Entity +import jetbrains.exodus.entitystore.StoreTransaction +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.json.* +import kotlinx.serialization.serializer + +internal fun StoreTransaction.decodeFromEntity(entity: Entity): JsonElement = buildJsonObject { + entity.propertyNames.forEach { property -> + entity.getProperty(property).let { value -> + when (value) { + is Number -> put(property, value) + is Boolean -> put(property, value) + is String -> put(property, value) + else -> throw IllegalStateException("Unsupported type for primitive field") + } + } + } + + entity.linkNames.forEach { link -> + entity.getLinks(link).let { entities -> + when (entities.size()) { + 1L -> entities.first?.let { put(link, decodeFromEntity(it)) } + else -> { + putJsonArray(link) { + entities.forEach { + add(decodeFromEntity(it)) + } + } + } + } + } + } +} + +public fun StoreTransaction.decodeFromEntity(entity: Entity, deserializer: DeserializationStrategy): T { + val jsonElement = decodeFromEntity(entity) + return Json.decodeFromJsonElement(deserializer, jsonElement) +} + +public inline fun StoreTransaction.decodeFromEntity(entity: Entity): T = decodeFromEntity(entity, serializer()) diff --git a/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/encoder.kt b/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/encoder.kt new file mode 100644 index 0000000..87173f9 --- /dev/null +++ b/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/encoder.kt @@ -0,0 +1,58 @@ +package ru.mipt.npm.xodus.serialization.json + +import jetbrains.exodus.entitystore.Entity +import jetbrains.exodus.entitystore.StoreTransaction +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.json.* +import kotlinx.serialization.serializer + +internal fun StoreTransaction.encodeToEntity(jsonElement: JsonElement, entity: Entity) { + when (jsonElement) { + is JsonPrimitive -> throw IllegalStateException("Can't serialize primitive value to entity") + is JsonArray -> throw IllegalStateException("Can't serialize array value to entity") + is JsonObject -> { + jsonElement.forEach { entry -> + entry.value.let { value -> + when(value) { + is JsonPrimitive -> { + if (value.isString) { + entity.setProperty(entry.key, value.content) + } else { + (value.longOrNull ?: value.doubleOrNull ?: value.booleanOrNull)?.let { + entity.setProperty( + entry.key, + it + ) + } + } + } + + // считаем, что все элементы массива - JsonObject, иначе не можем напрямую сериализовать (надо придывать костыли???) + is JsonArray -> { + value.forEach { element -> + val childEntity = newEntity("${entity.type}.${entry.key}") + encodeToEntity(element, childEntity) + entity.addLink(entry.key, childEntity) + } + } + + is JsonObject -> { + val childEntity = newEntity("${entity.type}.${entry.key}") + encodeToEntity(value, childEntity) + entity.setLink(entry.key, childEntity) + } + } + } + } + } + } +} + +public fun StoreTransaction.encodeToEntity(serializer: SerializationStrategy, value: T, entityType: String): Entity { + val entity: Entity = newEntity(entityType) + encodeToEntity(Json.encodeToJsonElement(serializer, value), entity) + return entity +} + +public inline fun StoreTransaction.encodeToEntity(value: T, entityType: String): Entity = + encodeToEntity(serializer(), value, entityType) diff --git a/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/main.kt b/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/main.kt new file mode 100644 index 0000000..ac11421 --- /dev/null +++ b/xodus-serialization/src/main/kotlin/ru/mipt/npm/xodus/serialization/json/main.kt @@ -0,0 +1,39 @@ +package ru.mipt.npm.xodus.serialization.json + +import jetbrains.exodus.entitystore.PersistentEntityStores +import kotlinx.datetime.Instant +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import ru.mipt.npm.controls.api.PropertyChangedMessage +import ru.mipt.npm.magix.api.MagixMessage +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.names.Name +import java.nio.file.Paths + +internal fun main() { + val expectedMessage = MagixMessage( + "dataforge", + "dataforge", + PropertyChangedMessage( + "acceleration", + Meta { + "x" put 3.0 + "y" put 9.0 + }, + Name.parse("virtual-car"), + Name.parse("magix-virtual-car"), + time = Instant.fromEpochMilliseconds(1337) + ), + "magix-virtual-car", + user = JsonObject(content = mapOf(Pair("name", JsonPrimitive("SCADA")))) + ) + + val entityStore = PersistentEntityStores.newInstance(Paths.get("xodus_serialization").toString()) + entityStore.executeInTransaction { txn -> + txn.encodeToEntity(expectedMessage, "MagixMessage") + } + + entityStore.executeInTransaction { txn -> + txn.getAll("MagixMessage").first?.let { println(txn.decodeFromEntity>(it) == expectedMessage) } + } +} \ No newline at end of file