ENT-11975: Updating config parsing with 4.12 version, which correctly parses the 4.12 rotated keys config item.

This commit is contained in:
Adel El-Beik 2024-10-13 19:26:01 +01:00
parent c9bbb5373a
commit 76b56679e6

View File

@ -3,7 +3,13 @@
package net.corda.nodeapi.internal.config package net.corda.nodeapi.internal.config
import com.typesafe.config.* import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigUtil
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.ConfigValueType
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.isStatic import net.corda.core.internal.isStatic
import net.corda.core.internal.noneOrSingle import net.corda.core.internal.noneOrSingle
@ -11,9 +17,7 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.ParameterizedType
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
@ -22,7 +26,9 @@ import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.temporal.Temporal import java.time.temporal.Temporal
import java.util.* import java.time.temporal.TemporalAmount
import java.util.Properties
import java.util.UUID
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -181,7 +187,7 @@ private fun Config.getSingleValue(
X500Principal::class -> X500Principal(getString(path)) X500Principal::class -> X500Principal(getString(path))
CordaX500Name::class -> { CordaX500Name::class -> {
when (getValue(path).valueType()) { when (getValue(path).valueType()) {
ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) as CordaX500Name
else -> CordaX500Name.parse(getString(path)) else -> CordaX500Name.parse(getString(path))
} }
} }
@ -291,98 +297,39 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
*/ */
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig() fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) { fun Any?.toConfigValue(): ConfigValue = ConfigValueFactory.fromAnyRef(sanitiseForFromAnyRef(this))
this
} else if (this != null) {
ConfigValueFactory.fromAnyRef(convertValue(this))
} else {
ConfigValueFactory.fromAnyRef(null)
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object. // Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
private fun Any.toConfigMap(): Map<String, Any> { private fun Any.toConfigMap(): Map<String, Any?> {
val values = HashMap<String, Any>() val values = LinkedHashMap<String, Any?>()
for (field in javaClass.declaredFields) { for (field in javaClass.declaredFields) {
if (field.isStatic || field.isSynthetic) continue if (field.isStatic || field.isSynthetic) continue
field.isAccessible = true field.isAccessible = true
val value = field.get(this) ?: continue val value = field.get(this) ?: continue
val configValue = if (value is String || value is Boolean || value is Number) { values[field.name] = sanitiseForFromAnyRef(value)
// These types are supported by Config as use as is
value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
value is Path || value is URL || value is UUID || value is X500Principal) {
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
value.toString()
} else if (value is Enum<*>) {
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
value.name
} else if (value is Properties) {
// For Properties we treat keys with . as nested configs
ConfigFactory.parseMap(uncheckedCast(value)).root()
} else if (value is Iterable<*>) {
value.toConfigIterable(field)
} else {
// Else this is a custom object recursed over
value.toConfigMap()
}
values[field.name] = configValue
} }
return values return values
} }
private fun convertValue(value: Any): Any { /**
* @see ConfigValueFactory.fromAnyRef
return if (value is String || value is Boolean || value is Number) { */
private fun sanitiseForFromAnyRef(value: Any?): Any? {
return when (value) {
// These types are supported by Config as use as is // These types are supported by Config as use as is
value is String, is Boolean, is Number, is ConfigValue, is Duration, null -> value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || is Enum<*> -> value.name
value is Path || value is URL || value is UUID || value is X500Principal) { // These types make sense to be represented as Strings
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs is Temporal, is TemporalAmount, is NetworkHostAndPort, is CordaX500Name, is Path, is URL, is UUID, is X500Principal -> value.toString()
value.toString()
} else if (value is Enum<*>) {
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
value.name
} else if (value is Properties) {
// For Properties we treat keys with . as nested configs // For Properties we treat keys with . as nested configs
ConfigFactory.parseMap(uncheckedCast(value)).root() is Properties -> ConfigFactory.parseMap(uncheckedCast(value)).root()
} else if (value is Iterable<*>) { is Map<*, *> -> ConfigFactory.parseMap(value.map { it.key.toString() to sanitiseForFromAnyRef(it.value) }.toMap()).root()
value.toConfigIterable() is Iterable<*> -> value.map(::sanitiseForFromAnyRef)
} else {
// Else this is a custom object recursed over // Else this is a custom object recursed over
value.toConfigMap() else -> value.toConfigMap()
} }
} }
// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
return when (elementType) {
// For the types already supported by Config we can use the Iterable as is
String::class.java -> this
Integer::class.java -> this
java.lang.Long::class.java -> this
java.lang.Double::class.java -> this
java.lang.Boolean::class.java -> this
LocalDate::class.java -> map(Any?::toString)
Instant::class.java -> map(Any?::toString)
NetworkHostAndPort::class.java -> map(Any?::toString)
Path::class.java -> map(Any?::toString)
URL::class.java -> map(Any?::toString)
X500Principal::class.java -> map(Any?::toString)
UUID::class.java -> map(Any?::toString)
CordaX500Name::class.java -> map(Any?::toString)
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
else -> if (elementType.isEnum) {
map { (it as Enum<*>).name }
} else {
map { it?.toConfigMap() }
}
}
}
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
// The typesafe .getBoolean function is case sensitive, this is a case insensitive version // The typesafe .getBoolean function is case sensitive, this is a case insensitive version
fun Config.getBooleanCaseInsensitive(path: String): Boolean { fun Config.getBooleanCaseInsensitive(path: String): Boolean {
try { try {