Skip to content

Commit

Permalink
Drop BasePluginConfig and convert interface Configurable into class f…
Browse files Browse the repository at this point in the history
…or simplicity
  • Loading branch information
martinvisser committed Sep 11, 2023
1 parent eb376bc commit 69492be
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 108 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.nio.file.Path
interface Config {
operator fun get(key: String): Any?
operator fun set(key: String, value: Any?)
fun load(): PluginConfig
fun configure(function: (ConfigurationProperty) -> Unit)
fun configFile(): Path
fun pluginConfigDirectory(): Path
Expand Down
108 changes: 106 additions & 2 deletions ret-core/src/main/kotlin/io/rabobank/ret/configuration/Configurable.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,114 @@
package io.rabobank.ret.configuration

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.convertValue
import com.fasterxml.jackson.module.kotlin.readValue
import io.quarkus.logging.Log
import io.rabobank.ret.RetConsole
import io.rabobank.ret.util.OsUtils
import jakarta.inject.Inject
import org.eclipse.microprofile.config.inject.ConfigProperty

/**
* Implement this interface in an ApplicationScoped class if you need custom configurations in your plugin.
* Upon initializing the plugin, the user will be prompted for all provided configuration properties,
* and inputs are saved in the RET configuration file.
*/
fun interface Configurable {
fun properties(): List<ConfigurationProperty>
open class Configurable {
@ConfigProperty(name = "plugin.name", defaultValue = "ret")
lateinit var pluginName: String

@Inject
lateinit var osUtils: OsUtils

@Inject
lateinit var objectMapper: ObjectMapper

@Inject
lateinit var retConfig: RetConfig

@Inject
lateinit var retConsole: RetConsole

private val configLoader by lazy { PluginConfigLoader(pluginName, objectMapper, osUtils) }

val pluginConfig by lazy { initialize() }

private fun initialize(): PluginConfig {
var configMigrated = false
val configCopy = configLoader.load()
val keysToMigrate = keysToMigrate()

if (keysToMigrate.isNotEmpty()) {
keysToMigrate.forEach { (oldKey, newKey) ->
val oldValue = retConfig[oldKey]
if (oldValue != null) {
configCopy[newKey] = oldValue

retConfig.remove(oldKey)
configMigrated = true
}
}
}

return if (configMigrated) {
Log.debug("Migrated old configuration to plugin specific configuration")
retConfig.save()
configLoader.saveAndReload(configCopy)
} else {
configCopy
}
}

fun load() = pluginConfig

open fun properties() = emptyList<ConfigurationProperty>()

/**
* You can provide a map of keys which RET can automatically migrate to the new plugin configuration.
*
* The left should be the name of the old key and the right the name of the new key
*/
open fun keysToMigrate() = emptyList<Pair<String, String>>()

/**
* Helper method to convert a simple Map to type-safe plugin specifc configuration.
*/
inline fun <reified T> convertTo() = objectMapper.convertValue<T>(pluginConfig.config)
}

class PluginConfig(val config: MutableMap<String, Any?>) {
inline operator fun <reified T> get(key: String): T? {
val value = config[key]
return if (value is T?) {
value
} else {
error(
"The config value '$key' cannot be cast to ${T::class.java}, because it's of type ${value?.javaClass}",
)
}
}

operator fun set(key: String, value: Any?) {
config[key] = value
}
}

class PluginConfigLoader(pluginName: String, private val objectMapper: ObjectMapper, osUtils: OsUtils) {
private val pluginFile = osUtils.getPluginConfig(pluginName).toFile()

fun load() = PluginConfig(
runCatching { objectMapper.readValue<Map<String, Any?>>(pluginFile) }
.getOrDefault(emptyMap())
.toMutableMap(),
)

fun saveAndReload(pluginConfig: PluginConfig): PluginConfig {
if (!pluginFile.parentFile.exists() && !pluginFile.parentFile.mkdirs()) {
error("Unable to create directory ${pluginFile.parentFile}")
}
objectMapper.writerWithDefaultPrettyPrinter().writeValue(pluginFile, pluginConfig.config)

return load()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class RetConfig(

if (configFile.exists()) {
properties = objectMapper.readValue<MutableMap<String, Any?>>(configFile)
if (properties[RET_VERSION] != retVersion) {
properties[RET_VERSION] = retVersion
save()
}
}
}

Expand Down Expand Up @@ -87,11 +91,18 @@ class RetConfig(
}

fun save() {
properties[RET_VERSION] = retVersion
objectMapper.writerWithDefaultPrettyPrinter().writeValue(configFile, properties)
}

override fun configFile(): Path = Path.of(configFile.toURI())

override fun pluginConfigDirectory(): Path = osUtils.getRetPluginsDirectory()

override fun load(): PluginConfig {
val pluginConfig = PluginConfig(mutableMapOf())
return configurables.fold(pluginConfig) { acc, configurable ->
acc.config.putAll(configurable.load().config)
acc
}
}
}
1 change: 1 addition & 0 deletions ret-core/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ quarkus.log.console.format=${quarkus.log.file.format}
#dev profile
%dev.quarkus.log.console.enable=true
%dev.quarkus.log.file.enable=false
%dev.quarkus.log.category."io.rabobank".level=debug
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.rabobank.ret.commands

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.quarkus.logging.Log
import io.rabobank.ret.RetConsole
import io.rabobank.ret.configuration.Config
Expand Down Expand Up @@ -52,14 +51,13 @@ class PluginConfigureCommand(
private fun storePluginConfiguration(pluginName: String) {
var hasPluginSpecificConfig = false
val pluginConfigFile = config.pluginConfigDirectory().resolve("$pluginName.json").toFile()
val pluginConfig =
if (pluginConfigFile.exists()) objectMapper.readValue<Map<String, Any?>>(pluginConfigFile) else emptyMap()
val answers = pluginConfig.toMutableMap()
val pluginConfig = config.load()
val answers = pluginConfig.config

config.configure {
hasPluginSpecificConfig = true
val message = it.toMessage()
val currentValue = (pluginConfig[it.key] as String?).orEmpty()
val currentValue = (answers[it.key] as String?).orEmpty()
var input = retConsole.prompt(message, currentValue)

while (it.required && input.ifEmpty { currentValue }.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class PluginConfigureCommandTest {
}

private fun storeConfig(demoConfig: Map<String, String>) {
objectMapper.writeValue(pluginsPath.resolve("$pluginName.json").toFile(), demoConfig)
demoConfig.forEach { (k, v) -> config[k] = v }
}

private fun readConfig() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.rabobank.ret.commands

import io.rabobank.ret.configuration.Config
import io.rabobank.ret.configuration.ConfigurationProperty
import io.rabobank.ret.configuration.PluginConfig
import java.nio.file.Path
import java.util.Properties

Expand All @@ -18,6 +19,9 @@ internal class TestConfig(private val pluginsPath: Path) : Config {
properties[key] = value
}

@Suppress("UNCHECKED_CAST")
override fun load(): PluginConfig = PluginConfig(properties as MutableMap<String, Any?>)

override fun configure(function: (ConfigurationProperty) -> Unit) {
configProps.forEach(function)
}
Expand Down

0 comments on commit 69492be

Please sign in to comment.