From 53d92771bb0c380c79457ac3e6633c96c8fd1d5f Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 10 Mar 2020 14:21:40 +0000 Subject: [PATCH] ENT-4494 Harmonize configuration classes. --- .../internal/config/CertificateStore.kt | 1 + .../internal/config/ConfigUtilities.kt | 97 +++++++++++++++---- .../internal/config/SslConfiguration.kt | 9 +- 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt index 3cecaa97c8..b5285c93cd 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt @@ -60,6 +60,7 @@ interface CertificateStore : Iterable> { forEach { (alias, certificate) -> action.invoke(alias, certificate) } } + fun aliases(): List = value.internal.aliases().toList() /** * @throws IllegalArgumentException if no certificate for the alias is found, or if the certificate is not an [X509Certificate]. */ diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index f9f2e0bde9..56f12e0dde 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -1,4 +1,5 @@ @file:JvmName("ConfigUtilities") +@file:Suppress("LongParameterList") package net.corda.nodeapi.internal.config @@ -52,21 +53,35 @@ const val CUSTOM_NODE_PROPERTIES_ROOT = "custom" // This is to enable constructs like: // `val keyStorePassword: String by config` operator fun Config.getValue(receiver: Any, metadata: KProperty<*>): T { - return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle, nestedPath = null, baseDirectory = null) + return getValueInternal( + metadata.name, + metadata.returnType, + UnknownConfigKeysPolicy.IGNORE::handle, + nestedPath = null, + baseDirectory = null + ) } // Problems: // - Forces you to have a primary constructor with all fields of name and type matching the configuration file structure. // - Encourages weak bean-like types. -// - Cannot support a many-to-one relationship between configuration file structures and configuration domain type. This is essential for versioning of the configuration files. +// - Cannot support a many-to-one relationship between configuration file structures and configuration domain type. This is essential for +// versioning of the configuration files. // - It's complicated and based on reflection, meaning problems with it are typically found at runtime. -// - It doesn't support validation errors in a structured way. If something goes wrong, it throws exceptions, which doesn't support good usability practices like displaying all the errors at once. -fun Config.parseAs(clazz: KClass, onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, - nestedPath: String? = null, baseDirectory: Path? = null): T { +// - It doesn't support validation errors in a structured way. If something goes wrong, it throws exceptions, which doesn't support good +// usability practices like displaying all the errors at once. +fun Config.parseAs( + clazz: KClass, + onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, + nestedPath: String? = null, + baseDirectory: Path? = null +): T { // Use custom parser if provided, instead of treating the object as data class. clazz.findAnnotation()?.let { return uncheckedCast(it.parser.createInstance().parse(this)) } - require(clazz.isData) { "Only Kotlin data classes or class annotated with CustomConfigParser can be parsed. Offending: ${clazz.qualifiedName}" } + require(clazz.isData) { + "Only Kotlin data classes or class annotated with CustomConfigParser can be parsed. Offending: ${clazz.qualifiedName}" + } val constructor = clazz.primaryConstructor!! val parameters = constructor.parameters val parameterNames = parameters.flatMap { param -> @@ -104,24 +119,46 @@ class UnknownConfigurationKeysException private constructor(val unknownKeys: Set companion object { fun of(offendingKeys: Set): UnknownConfigurationKeysException = UnknownConfigurationKeysException(offendingKeys) - private fun message(offendingKeys: Set) = "Unknown configuration keys: ${offendingKeys.joinToString(", ", "[", "]")}." + private fun message(offendingKeys: Set) = "Unknown configuration keys: " + + "${offendingKeys.joinToString(", ", "[", "]")}." } } -inline fun Config.parseAs(noinline onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T = parseAs(T::class, onUnknownKeys) +inline fun Config.parseAs( + noinline onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle +): T = parseAs(T::class, onUnknownKeys) fun Config.toProperties(): Properties { return entrySet().associateByTo( Properties(), { ConfigUtil.splitPath(it.key).joinToString(".") }, - { it.value.unwrapped().toString() }) + { it.value.unwrapped() }) } -private fun Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set, logger: Logger) -> Unit), nestedPath: String?, baseDirectory: Path?): T { - return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys, nestedPath, baseDirectory) else getCollectionValue(path, type, onUnknownKeys, nestedPath, baseDirectory)) +private fun Config.getValueInternal( + path: String, + type: KType, + onUnknownKeys: ((Set, logger: Logger) -> Unit), + nestedPath: String?, + baseDirectory: Path? +): T { + return uncheckedCast( + if (type.arguments.isEmpty()) { + getSingleValue(path, type, onUnknownKeys, nestedPath, baseDirectory) + } else { + getCollectionValue(path, type, onUnknownKeys, nestedPath, baseDirectory) + } + ) } -private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set, logger: Logger) -> Unit, nestedPath: String?, baseDirectory: Path?): Any? { +@Suppress("ComplexMethod") +private fun Config.getSingleValue( + path: String, + type: KType, + onUnknownKeys: (Set, logger: Logger) -> Unit, + nestedPath: String?, + baseDirectory: Path? +): Any? { if (type.isMarkedNullable && !hasPath(path)) return null val typeClass = type.jvmErasure return try { @@ -153,7 +190,12 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set else -> if (typeClass.java.isEnum) { parseEnum(typeClass.java, getString(path)) } else { - getConfig(path).parseAs(typeClass, onUnknownKeys, nestedPath?.let { "$it.$path" } ?: path, baseDirectory = baseDirectory) + getConfig(path).parseAs( + typeClass, + onUnknownKeys, + nestedPath?.let { "$it.$path" } ?: path, + baseDirectory = baseDirectory + ) } } } catch (e: ConfigException.Missing) { @@ -164,7 +206,8 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set private fun resolvePath(pathAsString: String, baseDirectory: Path?): Path { val path = Paths.get(pathAsString) return if (baseDirectory != null) { - // if baseDirectory been specified try resolving path against it. Note if `pathFromConfig` is an absolute path - this instruction has no effect. + // if baseDirectory been specified try resolving path against it. Note if `pathFromConfig` is an absolute path - this instruction + // has no effect. baseDirectory.resolve(path) } else { path @@ -178,10 +221,18 @@ private fun ConfigException.Missing.relative(path: String, nestedPath: String?): } } -private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set, logger: Logger) -> Unit, nestedPath: String?, baseDirectory: Path?): Collection { +@Suppress("ComplexMethod") +private fun Config.getCollectionValue( + path: String, + type: KType, + onUnknownKeys: (Set, logger: Logger) -> Unit, + nestedPath: String?, + baseDirectory: Path? +): Collection { 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") + val elementClass = type.arguments[0].type?.jvmErasure + ?: throw IllegalArgumentException("Cannot work with star projection: $type") if (!hasPath(path)) { return if (typeClass == List::class) emptyList() else emptySet() } @@ -240,7 +291,13 @@ private fun > enumBridge(clazz: Class, name: String): T { */ fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig() -fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) this else if (this != null) ConfigValueFactory.fromAnyRef(convertValue(this)) else ConfigValueFactory.fromAnyRef(null) +fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) { + 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. @@ -253,7 +310,8 @@ private fun Any.toConfigMap(): Map { val configValue = if (value is String || value is Boolean || value is Number) { // 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) { + } 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<*>) { @@ -278,7 +336,8 @@ private fun convertValue(value: Any): Any { return if (value is String || value is Boolean || value is Number) { // 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) { + } 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<*>) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt index 1c5a90e4bf..e4433b4e00 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt @@ -4,6 +4,7 @@ interface SslConfiguration { val keyStore: FileBasedCertificateStoreSupplier? val trustStore: FileBasedCertificateStoreSupplier? + val useOpenSsl: Boolean companion object { @@ -19,4 +20,10 @@ interface MutualSslConfiguration : SslConfiguration { override val trustStore: FileBasedCertificateStoreSupplier } -private class MutualSslOptions(override val keyStore: FileBasedCertificateStoreSupplier, override val trustStore: FileBasedCertificateStoreSupplier) : MutualSslConfiguration \ No newline at end of file +private class MutualSslOptions(override val keyStore: FileBasedCertificateStoreSupplier, + override val trustStore: FileBasedCertificateStoreSupplier) : MutualSslConfiguration { + override val useOpenSsl: Boolean = false +} + +const val DEFAULT_SSL_HANDSHAKE_TIMEOUT_MILLIS = 60000L // Set at least 3 times higher than sun.security.provider.certpath.URICertStore.DEFAULT_CRL_CONNECT_TIMEOUT which is 15 sec +