[CORDA-1379]: Allow soft trapping of unknown config keys. (#3011)

This commit is contained in:
Michele Sollecito
2018-04-27 15:33:13 +07:00
committed by GitHub
parent efa69ce959
commit 235df69efe
6 changed files with 61 additions and 23 deletions

View File

@ -7,6 +7,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NetworkHostAndPort
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
@ -35,10 +36,10 @@ const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
// TODO Move other config parsing to use parseAs and remove this
operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
return getValueInternal(metadata.name, metadata.returnType)
return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle)
}
fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T {
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
val constructor = clazz.primaryConstructor!!
val parameters = constructor.parameters
@ -55,14 +56,13 @@ fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
.filterNot { it == CUSTOM_NODE_PROPERTIES_ROOT }
.filterNot(parameterNames::contains)
.toSortedSet()
if (unknownConfigurationKeys.isNotEmpty()) {
throw UnknownConfigurationKeysException.of(unknownConfigurationKeys)
}
onUnknownKeys.invoke(unknownConfigurationKeys, logger)
val args = parameters.filterNot { it.isOptional && !hasPath(it.name!!) }.associateBy({ it }) { param ->
// Get the matching property for this parameter
val property = clazz.memberProperties.first { it.name == param.name }
val path = defaultToOldPath(property)
getValueInternal<Any>(path, param.type)
getValueInternal<Any>(path, param.type, onUnknownKeys)
}
try {
return constructor.callBy(args)
@ -82,7 +82,7 @@ class UnknownConfigurationKeysException private constructor(val unknownKeys: Set
}
}
inline fun <reified T : Any> Config.parseAs(): T = parseAs(T::class)
inline fun <reified T : Any> Config.parseAs(noinline onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T = parseAs(T::class, onUnknownKeys)
fun Config.toProperties(): Properties {
return entrySet().associateByTo(
@ -91,11 +91,11 @@ fun Config.toProperties(): Properties {
{ it.value.unwrapped().toString() })
}
private fun <T : Any> Config.getValueInternal(path: String, type: KType): T {
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type))
private fun <T : Any> Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit)): T {
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys) else getCollectionValue(path, type, onUnknownKeys))
}
private fun Config.getSingleValue(path: String, type: KType): Any? {
private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit): Any? {
if (type.isMarkedNullable && !hasPath(path)) return null
val typeClass = type.jvmErasure
return when (typeClass) {
@ -113,7 +113,7 @@ private fun Config.getSingleValue(path: String, type: KType): Any? {
UUID::class -> UUID.fromString(getString(path))
CordaX500Name::class -> {
when (getValue(path).valueType()) {
ConfigValueType.OBJECT -> getConfig(path).parseAs()
ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
else -> CordaX500Name.parse(getString(path))
}
}
@ -122,12 +122,12 @@ private fun Config.getSingleValue(path: String, type: KType): Any? {
else -> if (typeClass.java.isEnum) {
parseEnum(typeClass.java, getString(path))
} else {
getConfig(path).parseAs(typeClass)
getConfig(path).parseAs(typeClass, onUnknownKeys)
}
}
}
private fun Config.getCollectionValue(path: String, type: KType): Collection<Any> {
private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit): Collection<Any> {
val typeClass = type.jvmErasure
require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
@ -151,7 +151,7 @@ private fun Config.getCollectionValue(path: String, type: KType): Collection<Any
else -> if (elementClass.java.isEnum) {
getStringList(path).map { parseEnum(elementClass.java, it) }
} else {
getConfigList(path).map { it.parseAs(elementClass) }
getConfigList(path).map { it.parseAs(elementClass, onUnknownKeys) }
}
}
return if (typeClass == Set::class) values.toSet() else values
@ -242,3 +242,17 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
}
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config")
enum class UnknownConfigKeysPolicy(private val handle: (Set<String>, logger: Logger) -> Unit) {
FAIL({ unknownKeys, _ -> throw UnknownConfigurationKeysException.of(unknownKeys) }),
WARN({ unknownKeys, logger -> logger.warn("Unknown configuration keys found: ${unknownKeys.joinToString(", ", "[", "]")}.") }),
IGNORE({ _, _ -> });
fun handle(unknownKeys: Set<String>, logger: Logger) {
if (unknownKeys.isNotEmpty()) {
handle.invoke(unknownKeys, logger)
}
}
}