Archived
0

Clean up code for polymorphic options serialization, fix logger configuration

This commit is contained in:
Dico
2018-08-03 03:25:52 +01:00
parent 7cd9844670
commit 703e02d6b2
19 changed files with 324 additions and 331 deletions

View File

@@ -72,7 +72,8 @@ dependencies {
compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false } compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false }
compile("joda-time:joda-time:2.10") compile("joda-time:joda-time:2.10")
compile("com.zaxxer:HikariCP:3.2.0") compile("com.zaxxer:HikariCP:3.2.0")
compile("ch.qos.logback:logback-classic:1.2.3") compile("ch.qos.logback:logback-classic:1.2.3") { isTransitive = false }
compile("ch.qos.logback:logback-core:1.2.3") { isTransitive = false }
val jacksonVersion = "2.9.6" val jacksonVersion = "2.9.6"
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion") compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")

View File

@@ -1,104 +0,0 @@
package io.dico.parcels2
import com.fasterxml.jackson.annotation.JsonIgnore
import io.dico.parcels2.blockvisitor.TickWorktimeOptions
import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageFactory
import io.dico.parcels2.storage.yamlObjectMapper
import org.bukkit.GameMode
import org.bukkit.Material
import java.io.Reader
import java.io.Writer
import java.util.EnumSet
class Options {
var worlds: Map<String, WorldOptionsHolder> = hashMapOf()
private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
fun addWorld(name: String,
generatorOptions: GeneratorOptions? = null,
worldOptions: WorldOptions? = null) {
val optionsHolder = WorldOptionsHolder(
generatorOptions ?: DefaultGeneratorOptions(),
worldOptions ?: WorldOptions()
)
(worlds as MutableMap).put(name, optionsHolder)
}
fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this)
fun mergeFrom(reader: Reader) = yamlObjectMapper.readerForUpdating(this).readValue<Options>(reader)
override fun toString(): String = yamlObjectMapper.writeValueAsString(this)
}
class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(),
var runtime: WorldOptions = WorldOptions())
data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true,
var noWeather: Boolean = true,
var preventWeatherBlockChanges: Boolean = true,
var preventBlockSpread: Boolean = true, // TODO
var dropEntityItems: Boolean = true,
var doTileDrops: Boolean = false,
var disableExplosions: Boolean = true,
var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10) {
}
abstract class GeneratorOptions {
abstract fun generatorFactory(): GeneratorFactory
fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this)
}
class StorageOptions(val dialect: String,
val options: Any) {
@get:JsonIgnore
val factory = StorageFactory.getFactory(dialect)
?: throw IllegalArgumentException("Invalid storage dialect: $dialect")
fun newStorageInstance(): Storage = factory.newStorageInstance(dialect, options)
}
data class DataConnectionOptions(val address: String = "localhost",
val database: String = "parcels",
val username: String = "root",
val password: String = "",
val poolSize: Int = 4) {
fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? {
val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort)
val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also {
logger.error("(Invalidly) blank address in data storage options")
}
val port = address.substring(idx + 1).toIntOrNull() ?: return null.also {
logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
}
return Pair(addressName, port)
}
}
data class DataFileOptions(val location: String = "/flatfile-storage/")
class MigrationOptions() {
}

View File

@@ -4,6 +4,7 @@ import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.defaultimpl.DefaultParcelGenerator import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
import io.dico.parcels2.options.GeneratorOptions
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
@@ -17,26 +18,6 @@ import java.util.HashMap
import java.util.Random import java.util.Random
import kotlin.reflect.KClass import kotlin.reflect.KClass
object GeneratorFactories {
private val map: MutableMap<String, GeneratorFactory> = HashMap()
fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null
fun getFactory(name: String): GeneratorFactory? = map.get(name)
init {
registerFactory(DefaultParcelGenerator.Factory)
}
}
interface GeneratorFactory {
val name: String
val optionsClass: KClass<out GeneratorOptions>
fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator
}
abstract class ParcelGenerator : ChunkGenerator() { abstract class ParcelGenerator : ChunkGenerator() {
abstract val world: World abstract val world: World

View File

@@ -1,5 +1,6 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
@@ -76,7 +77,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
val id: ParcelWorldId val id: ParcelWorldId
val name: String val name: String
val uid: UUID? val uid: UUID?
val options: WorldOptions val options: RuntimeWorldOptions
val generator: ParcelGenerator val generator: ParcelGenerator
val storage: Storage val storage: Storage
val container: ParcelContainer val container: ParcelContainer

View File

@@ -10,8 +10,9 @@ import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.defaultimpl.ParcelProviderImpl
import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.options.Options
import io.dico.parcels2.options.optionsMapper
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.FunctionHelper
import io.dico.parcels2.util.tryCreate import io.dico.parcels2.util.tryCreate
import org.bukkit.Bukkit import org.bukkit.Bukkit
@@ -60,7 +61,7 @@ class ParcelsPlugin : JavaPlugin() {
if (!loadOptions()) return false if (!loadOptions()) return false
try { try {
storage = options.storage.newStorageInstance() storage = options.storage.newInstance()
storage.init() storage.init()
} catch (ex: Exception) { } catch (ex: Exception) {
plogger.error("Failed to connect to database", ex) plogger.error("Failed to connect to database", ex)
@@ -83,11 +84,11 @@ class ParcelsPlugin : JavaPlugin() {
fun loadOptions(): Boolean { fun loadOptions(): Boolean {
when { when {
optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile) optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
optionsFile.tryCreate() -> { optionsFile.tryCreate() -> {
options.addWorld("parcels") options.addWorld("parcels")
try { try {
yamlObjectMapper.writeValue(optionsFile, options) optionsMapper.writeValue(optionsFile, options)
} catch (ex: Throwable) { } catch (ex: Throwable) {
optionsFile.delete() optionsFile.delete()
throw ex throw ex

View File

@@ -4,6 +4,7 @@ import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.DefaultGeneratorOptions
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.* import org.bukkit.*
import org.bukkit.block.Biome import org.bukkit.block.Biome
@@ -17,21 +18,6 @@ import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR) private val airType = Bukkit.createBlockData(Material.AIR)
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
var parcelSize: Int = 101,
var pathSize: Int = 9,
var floorHeight: Int = 64,
var offsetX: Int = 0,
var offsetZ: Int = 0) : GeneratorOptions() {
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
}
class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
private var _world: World? = null private var _world: World? = null
override val world: World override val world: World
@@ -44,15 +30,6 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
} }
private var maxHeight = 0 private var maxHeight = 0
companion object Factory : GeneratorFactory {
override val name get() = "default"
override val optionsClass get() = DefaultGeneratorOptions::class
override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator {
return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions)
}
}
val sectionSize = o.parcelSize + o.pathSize val sectionSize = o.parcelSize + o.pathSize
val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
val makePathMain = o.pathSize > 2 val makePathMain = o.pathSize > 2

View File

@@ -28,7 +28,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
override fun getWorldGenerator(worldName: String): ParcelGenerator? { override fun getWorldGenerator(worldName: String): ParcelGenerator? {
return _worlds[worldName]?.generator return _worlds[worldName]?.generator
?: _generators[worldName] ?: _generators[worldName]
?: options.worlds[worldName]?.generator?.newGenerator(worldName)?.also { _generators[worldName] = it } ?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it }
} }
override fun loadWorlds() { override fun loadWorlds() {

View File

@@ -4,6 +4,7 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import org.bukkit.World import org.bukkit.World
import java.util.UUID import java.util.UUID
@@ -11,7 +12,7 @@ import java.util.UUID
class ParcelWorldImpl private class ParcelWorldImpl private
constructor(override val world: World, constructor(override val world: World,
override val generator: ParcelGenerator, override val generator: ParcelGenerator,
override var options: WorldOptions, override var options: RuntimeWorldOptions,
override val storage: Storage, override val storage: Storage,
override val globalAddedData: GlobalAddedDataManager, override val globalAddedData: GlobalAddedDataManager,
containerFactory: ParcelContainerFactory, containerFactory: ParcelContainerFactory,
@@ -62,7 +63,7 @@ constructor(override val world: World,
// Use this to be able to delegate blockManager and assign it to a property too, at least. // Use this to be able to delegate blockManager and assign it to a property too, at least.
operator fun invoke(world: World, operator fun invoke(world: World,
generator: ParcelGenerator, generator: ParcelGenerator,
options: WorldOptions, options: RuntimeWorldOptions,
storage: Storage, storage: Storage,
globalAddedData: GlobalAddedDataManager, globalAddedData: GlobalAddedDataManager,
containerFactory: ParcelContainerFactory, containerFactory: ParcelContainerFactory,

View File

@@ -0,0 +1,36 @@
package io.dico.parcels2.options
import io.dico.parcels2.ParcelGenerator
import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.block.Biome
import org.bukkit.block.data.BlockData
import kotlin.reflect.KClass
object GeneratorOptionsFactories : PolymorphicOptionsFactories<ParcelGenerator>("name", GeneratorOptions::class, DefaultGeneratorOptionsFactory())
class GeneratorOptions(name: String, options: Any) : PolymorphicOptions<ParcelGenerator>(name, options, GeneratorOptionsFactories) {
fun newInstance(worldName: String) = factory.newInstance(key, options, worldName)
}
private class DefaultGeneratorOptionsFactory : PolymorphicOptionsFactory<ParcelGenerator> {
override val supportedKeys: List<String> = listOf("default")
override val optionsClass: KClass<out Any> get() = DefaultGeneratorOptions::class
override fun newInstance(key: String, options: Any, vararg extra: Any?): ParcelGenerator {
return DefaultParcelGenerator(extra.first() as String, options as DefaultGeneratorOptions)
}
}
class DefaultGeneratorOptions(val defaultBiome: Biome = Biome.JUNGLE,
val wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
val floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
val fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
val pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
val pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
val parcelSize: Int = 101,
val pathSize: Int = 9,
val floorHeight: Int = 64,
val offsetX: Int = 0,
val offsetZ: Int = 0)

View File

@@ -0,0 +1,17 @@
package io.dico.parcels2.options
import io.dico.parcels2.storage.migration.Migration
import kotlin.reflect.KClass
object MigrationOptionsFactories : PolymorphicOptionsFactories<Migration>("kind", MigrationOptions::class, PlotmeMigrationFactory())
class MigrationOptions(kind: String, options: Any) : SimplePolymorphicOptions<Migration>(kind, options, MigrationOptionsFactories)
private class PlotmeMigrationFactory : PolymorphicOptionsFactory<Migration> {
override val supportedKeys = listOf("plotme-0.17")
override val optionsClass: KClass<out Any> get() = TODO()
override fun newInstance(key: String, options: Any, vararg extra: Any?): Migration {
TODO()
}
}

View File

@@ -0,0 +1,51 @@
package io.dico.parcels2.options
import io.dico.parcels2.blockvisitor.TickWorktimeOptions
import org.bukkit.GameMode
import org.bukkit.Material
import java.io.Reader
import java.io.Writer
import java.util.EnumSet
class Options {
var worlds: Map<String, WorldOptions> = hashMapOf()
private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
fun addWorld(name: String,
generatorOptions: GeneratorOptions? = null,
worldOptions: RuntimeWorldOptions? = null) {
val optionsHolder = WorldOptions(
generatorOptions ?: GeneratorOptions("default", DefaultGeneratorOptions()),
worldOptions ?: RuntimeWorldOptions()
)
(worlds as MutableMap).put(name, optionsHolder)
}
fun writeTo(writer: Writer) = optionsMapper.writeValue(writer, this)
fun mergeFrom(reader: Reader) = optionsMapper.readerForUpdating(this).readValue<Options>(reader)
override fun toString(): String = optionsMapper.writeValueAsString(this)
}
class WorldOptions(val generator: GeneratorOptions,
var runtime: RuntimeWorldOptions = RuntimeWorldOptions())
class RuntimeWorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true,
var noWeather: Boolean = true,
var preventWeatherBlockChanges: Boolean = true,
var preventBlockSpread: Boolean = true, // TODO
var dropEntityItems: Boolean = true,
var doTileDrops: Boolean = false,
var disableExplosions: Boolean = true,
var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10)
class DataFileOptions(val location: String = "/flatfile-storage/")

View File

@@ -0,0 +1,66 @@
package io.dico.parcels2.options
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.bukkit.Bukkit
import org.bukkit.block.data.BlockData
val optionsMapper = ObjectMapper(YAMLFactory()).apply {
propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE
val kotlinModule = KotlinModule()
with(kotlinModule) {
/*
setSerializerModifier(object : BeanSerializerModifier() {
@Suppress("UNCHECKED_CAST")
override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) {
GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
} else {
serializer
}
return super.modifySerializer(config, beanDesc, newSerializer)
}
})*/
addSerializer(BlockDataSerializer())
addDeserializer(BlockData::class.java, BlockDataDeserializer())
GeneratorOptionsFactories.registerSerialization(this)
StorageOptionsFactories.registerSerialization(this)
MigrationOptionsFactories.registerSerialization(this)
}
registerModule(kotlinModule)
}
private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) {
override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.asString)
}
}
private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? {
try {
return Bukkit.createBlockData(p.valueAsString)
} catch (ex: Exception) {
throw RuntimeException("Exception occurred at ${p.currentLocation}", ex)
}
}
}

View File

@@ -0,0 +1,89 @@
package io.dico.parcels2.options
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import io.dico.parcels2.logger
import kotlin.reflect.KClass
abstract class PolymorphicOptions<T : Any>(val key: String,
val options: Any,
factories: PolymorphicOptionsFactories<T>) {
val factory = factories.getFactory(key)!!
}
abstract class SimplePolymorphicOptions<T : Any>(key: String, options: Any, factories: PolymorphicOptionsFactories<T>)
: PolymorphicOptions<T>(key, options, factories) {
fun newInstance(): T = factory.newInstance(key, options)
}
interface PolymorphicOptionsFactory<T : Any> {
val supportedKeys: List<String>
val optionsClass: KClass<out Any>
fun newInstance(key: String, options: Any, vararg extra: Any?): T
}
@Suppress("UNCHECKED_CAST")
abstract class PolymorphicOptionsFactories<T : Any>(val serializeKeyAs: String,
rootClass: KClass<out PolymorphicOptions<T>>,
vararg defaultFactories: PolymorphicOptionsFactory<T>) {
val rootClass = rootClass as KClass<PolymorphicOptions<T>>
private val map: MutableMap<String, PolymorphicOptionsFactory<T>> = linkedMapOf()
val availableKeys: Collection<String> get() = map.keys
fun registerFactory(factory: PolymorphicOptionsFactory<T>) = factory.supportedKeys.forEach { map.putIfAbsent(it.toLowerCase(), factory) }
fun getFactory(key: String): PolymorphicOptionsFactory<T>? = map[key.toLowerCase()]
fun registerSerialization(module: SimpleModule) {
module.addSerializer(PolymorphicOptionsSerializer(this))
module.addDeserializer(rootClass.java, PolymorphicOptionsDeserializer(this))
}
init {
defaultFactories.forEach { registerFactory(it) }
}
}
private class PolymorphicOptionsDeserializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : JsonDeserializer<PolymorphicOptions<T>>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): PolymorphicOptions<T> {
val node = p.readValueAsTree<JsonNode>()
val key = node.get(factories.serializeKeyAs).asText()
val factory = getFactory(key)
val optionsNode = node.get("options")
val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java)
return factories.rootClass.constructors.first().call(key, options)
}
private fun getFactory(key: String): PolymorphicOptionsFactory<T> {
factories.getFactory(key)?.let { return it }
logger.warn("Unknown ${factories.rootClass.simpleName} ${factories.serializeKeyAs}: $key. " +
"\nAvailable options: ${factories.availableKeys}")
val default = factories.getFactory(factories.availableKeys.first())
?: throw IllegalStateException("No default ${factories.rootClass.simpleName} factory registered.")
return default
}
}
private class PolymorphicOptionsSerializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : StdSerializer<PolymorphicOptions<T>>(factories.rootClass.java) {
override fun serialize(value: PolymorphicOptions<T>, gen: JsonGenerator, sp: SerializerProvider?) {
with(gen) {
writeStartObject()
writeStringField(factories.serializeKeyAs, value.key)
writeFieldName("options")
writeObject(value.options)
writeEndObject()
}
}
}

View File

@@ -0,0 +1,45 @@
package io.dico.parcels2.options
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.logger
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageWithCoroutineBacking
import io.dico.parcels2.storage.exposed.ExposedBacking
import io.dico.parcels2.storage.getHikariConfig
object StorageOptionsFactories : PolymorphicOptionsFactories<Storage>("dialect", StorageOptions::class, ConnectionStorageFactory())
class StorageOptions(dialect: String, options: Any) : SimplePolymorphicOptions<Storage>(dialect, options, StorageOptionsFactories)
private class ConnectionStorageFactory : PolymorphicOptionsFactory<Storage> {
override val optionsClass = DataConnectionOptions::class
override val supportedKeys: List<String> = listOf("postgresql", "mariadb")
override fun newInstance(key: String, options: Any, vararg extra: Any?): Storage {
val hikariConfig = getHikariConfig(key, options as DataConnectionOptions)
val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}
}
data class DataConnectionOptions(val address: String = "localhost",
val database: String = "parcels",
val username: String = "root",
val password: String = "",
val poolSize: Int = 4) {
fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? {
val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort)
val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also {
logger.error("(Invalidly) blank address in data storage options")
}
val port = address.substring(idx + 1).toIntOrNull() ?: return null.also {
logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
}
return Pair(addressName, port)
}
}

View File

@@ -1,7 +1,7 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import io.dico.parcels2.DataConnectionOptions import io.dico.parcels2.options.DataConnectionOptions
fun getHikariConfig(dialectName: String, fun getHikariConfig(dialectName: String,
dco: DataConnectionOptions): HikariConfig = HikariConfig().apply { dco: DataConnectionOptions): HikariConfig = HikariConfig().apply {

View File

@@ -1,122 +0,0 @@
package io.dico.parcels2.storage
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import io.dico.parcels2.GeneratorFactories
import io.dico.parcels2.GeneratorOptions
import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit
import org.bukkit.block.data.BlockData
import kotlin.reflect.full.isSuperclassOf
val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply {
propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE
val kotlinModule = KotlinModule()
with(kotlinModule) {
setSerializerModifier(object : BeanSerializerModifier() {
@Suppress("UNCHECKED_CAST")
override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) {
GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
} else {
serializer
}
return super.modifySerializer(config, beanDesc, newSerializer)
}
})
addSerializer(BlockDataSerializer())
addDeserializer(BlockData::class.java, BlockDataDeserializer())
addSerializer(StorageOptionsSerializer())
addDeserializer(StorageOptions::class.java, StorageOptionsDeserializer())
addDeserializer(GeneratorOptions::class.java, GeneratorOptionsDeserializer())
}
registerModule(kotlinModule)
}
private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) {
override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.asString)
}
}
private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? {
try {
return Bukkit.createBlockData(p.valueAsString)
} catch (ex: Exception) {
throw RuntimeException("Exception occurred at ${p.currentLocation}", ex)
}
}
}
class StorageOptionsDeserializer : JsonDeserializer<StorageOptions>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): StorageOptions {
val node = p!!.readValueAsTree<JsonNode>()
val dialect = node.get("dialect").asText()
val optionsNode = node.get("options")
val factory = StorageFactory.getFactory(dialect) ?: throw IllegalStateException("Unknown storage dialect: $dialect")
val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java)
return StorageOptions(dialect, options)
}
}
class StorageOptionsSerializer : StdSerializer<StorageOptions>(StorageOptions::class.java) {
override fun serialize(value: StorageOptions?, gen: JsonGenerator?, serializers: SerializerProvider?) {
with(gen!!) {
writeStartObject()
writeStringField("dialect", value!!.dialect)
writeFieldName("options")
writeObject(value.options)
writeEndObject()
}
}
}
class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
override fun deserialize(parser: JsonParser?, ctx: DeserializationContext?): GeneratorOptions? {
val node = parser!!.readValueAsTree<JsonNode>()
val name = node.get("name").asText()
val optionsNode = node.get("options")
val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
}
}
class GeneratorOptionsSerializer(private val defaultSerializer: JsonSerializer<GeneratorOptions>) : JsonSerializer<GeneratorOptions>() {
override fun serialize(input: GeneratorOptions?, generator: JsonGenerator?, provider: SerializerProvider?) {
with(generator!!) {
writeStartObject()
writeStringField("name", input!!.generatorFactory().name)
writeFieldName("options")
defaultSerializer.serialize(input, generator, provider)
writeEndObject()
}
}
}

View File

@@ -1,43 +0,0 @@
package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions
import io.dico.parcels2.storage.exposed.ExposedBacking
import kotlin.reflect.KClass
interface StorageFactory {
companion object StorageFactories {
private val map: MutableMap<String, StorageFactory> = HashMap()
fun registerFactory(dialect: String, generator: StorageFactory): Boolean = map.putIfAbsent(dialect.toLowerCase(), generator) == null
fun getFactory(dialect: String): StorageFactory? = map[dialect.toLowerCase()]
init {
// have to write the code like this in kotlin.
// This code is absolutely disgusting
ConnectionStorageFactory().register(this)
}
}
val optionsClass: KClass<out Any>
fun newStorageInstance(dialect: String, options: Any): Storage
}
class ConnectionStorageFactory : StorageFactory {
override val optionsClass = DataConnectionOptions::class
private val types: List<String> = listOf("postgresql", "mariadb")
fun register(companion: StorageFactory.StorageFactories) {
types.forEach { companion.registerFactory(it, this) }
}
override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}
}

View File

@@ -1,5 +0,0 @@
package io.dico.parcels2.storage.migration
interface MigrationFactory {
fun getMigration()
}

View File

@@ -1,11 +1,12 @@
<configuration debug="true"> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg</pattern> <!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg</pattern>-->
<pattern>%magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="info"> <root level="debug">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>
</configuration> </configuration>