From 36fdb858c6ecc5c15f9b0ec9c4a77c0d71593bfc Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 28 Nov 2018 14:36:26 +0000 Subject: [PATCH 1/3] CORDA-2239: DriverDSL.startNode overload cleanup (#4252) The overload that takes in a bunch of the node parameters with default values has been fixed to the V3 version. New node parameters since V3 now only exist in NodeParameters. The reason for this is otherwise each new release of Corda that introduces new node parameters will force a new startNode overload to be added to DriverDSL to preserve backwards compatibility. NodeParameters has been moved to its own file and logLevel is removed as it doesn't do anything. --- .../node/flows/AsymmetricCorDappsTests.kt | 23 ++- ...owCheckpointVersionNodeStartupCheckTest.kt | 7 +- .../net/corda/node/flows/FlowOverrideTests.kt | 35 ++-- .../node/services/AttachmentLoadingTests.kt | 6 +- .../kotlin/net/corda/testing/driver/Driver.kt | 177 ------------------ .../net/corda/testing/driver/DriverDSL.kt | 82 ++++---- .../corda/testing/driver/NodeParameters.kt | 86 +++++++++ .../testing/node/internal/DriverDSLImpl.kt | 107 +++-------- .../net/corda/explorer/ExplorerSimulation.kt | 8 +- 9 files changed, 203 insertions(+), 328 deletions(-) create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt b/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt index 6e1e07d8ee..e5c1510c16 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt @@ -11,6 +11,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.driver import net.corda.testing.node.internal.cordappForClasses import org.junit.Test @@ -47,8 +48,14 @@ class AsymmetricCorDappsTests { @Test fun `no shared cordapps with asymmetric specific classes`() { driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) { - val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java))).getOrThrow() - val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java, Pong::class.java))).getOrThrow() + val nodeA = startNode(NodeParameters( + providedName = ALICE_NAME, + additionalCordapps = setOf(cordappForClasses(Ping::class.java)) + )).getOrThrow() + val nodeB = startNode(NodeParameters( + providedName = BOB_NAME, + additionalCordapps = setOf(cordappForClasses(Ping::class.java, Pong::class.java)) + )).getOrThrow() nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() } } @@ -58,8 +65,10 @@ class AsymmetricCorDappsTests { val sharedCordapp = cordappForClasses(Ping::class.java) val cordappForNodeB = cordappForClasses(Pong::class.java) driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) { - - val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() + val (nodeA, nodeB) = listOf( + startNode(NodeParameters(providedName = ALICE_NAME)), + startNode(NodeParameters(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))) + ).transpose().getOrThrow() nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() } } @@ -69,8 +78,10 @@ class AsymmetricCorDappsTests { val sharedCordapp = cordappForClasses(Ping::class.java) val cordappForNodeB = cordappForClasses(Pong::class.java) driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) { - - val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() + val (nodeA, nodeB) = listOf( + startNode(NodeParameters(providedName = ALICE_NAME)), + startNode(NodeParameters(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))) + ).transpose().getOrThrow() nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() } } diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt index 40e84e57fb..9c9dbe06eb 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt @@ -15,6 +15,7 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.driver import net.corda.testing.node.TestCordapp import net.corda.testing.node.internal.ListenProcessDeathException @@ -106,7 +107,7 @@ class FlowCheckpointVersionNodeStartupCheckTest { private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set) { val (alice, bob) = listOf(ALICE_NAME, BOB_NAME) - .map { startNode(providedName = it, additionalCordapps = cordapps) } + .map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) } .transpose() .getOrThrow() alice.stop() @@ -118,12 +119,12 @@ class FlowCheckpointVersionNodeStartupCheckTest { private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection, logMessage: String) { assertFailsWith(ListenProcessDeathException::class) { - startNode( + startNode(NodeParameters( providedName = BOB_NAME, customOverrides = mapOf("devMode" to false), additionalCordapps = cordapps, regenerateCordappsOnStart = true - ).getOrThrow() + )).getOrThrow() } val logDir = baseDirectory(BOB_NAME) / NodeStartup.LOGS_DIRECTORY_NAME diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/FlowOverrideTests.kt b/node/src/integration-test/kotlin/net/corda/node/flows/FlowOverrideTests.kt index b08dea48d8..fab3bf4f1a 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/FlowOverrideTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowOverrideTests.kt @@ -10,10 +10,11 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.driver import net.corda.testing.node.internal.cordappForClasses import org.hamcrest.CoreMatchers.`is` -import org.junit.Assert +import org.junit.Assert.assertThat import org.junit.Test class FlowOverrideTests { @@ -31,7 +32,7 @@ class FlowOverrideTests { @InitiatedBy(Ping::class) open class Pong(private val pingSession: FlowSession) : FlowLogic() { companion object { - val PONG = "PONG" + const val PONG = "PONG" } @Suspendable @@ -52,7 +53,7 @@ class FlowOverrideTests { class Pongiest(private val pingSession: FlowSession) : Pong(pingSession) { companion object { - val GORGONZOLA = "Gorgonzola" + const val GORGONZOLA = "Gorgonzola" } @Suspendable @@ -61,16 +62,21 @@ class FlowOverrideTests { } } - private val nodeAClasses = setOf(Ping::class.java, - Pong::class.java, Pongiest::class.java) + private val nodeAClasses = setOf(Ping::class.java, Pong::class.java, Pongiest::class.java) private val nodeBClasses = setOf(Ping::class.java, Pong::class.java) @Test fun `should use the most specific implementation of a responding flow`() { driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = emptySet())) { - val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray()))).getOrThrow() - val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray()))).getOrThrow() - Assert.assertThat(nodeB.rpc.startFlow(::Ping, nodeA.nodeInfo.singleIdentity()).returnValue.getOrThrow(), `is`(net.corda.node.flows.FlowOverrideTests.Pongiest.GORGONZOLA)) + val nodeA = startNode(NodeParameters( + providedName = ALICE_NAME, + additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray())) + )).getOrThrow() + val nodeB = startNode(NodeParameters( + providedName = BOB_NAME, + additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray())) + )).getOrThrow() + assertThat(nodeB.rpc.startFlow(::Ping, nodeA.nodeInfo.singleIdentity()).returnValue.getOrThrow(), `is`(Pongiest.GORGONZOLA)) } } @@ -78,9 +84,16 @@ class FlowOverrideTests { fun `should use the overriden implementation of a responding flow`() { val flowOverrides = mapOf(Ping::class.java to Pong::class.java) driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = emptySet())) { - val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray())), flowOverrides = flowOverrides).getOrThrow() - val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray()))).getOrThrow() - Assert.assertThat(nodeB.rpc.startFlow(::Ping, nodeA.nodeInfo.singleIdentity()).returnValue.getOrThrow(), `is`(net.corda.node.flows.FlowOverrideTests.Pong.PONG)) + val nodeA = startNode(NodeParameters( + providedName = ALICE_NAME, + additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray())), + flowOverrides = flowOverrides + )).getOrThrow() + val nodeB = startNode(NodeParameters( + providedName = BOB_NAME, + additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray())) + )).getOrThrow() + assertThat(nodeB.rpc.startFlow(::Ping, nodeA.nodeInfo.singleIdentity()).returnValue.getOrThrow(), `is`(Pong.PONG)) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 59c9340476..e2ea6ff0d1 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -28,6 +28,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.driver import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.rigorousMock @@ -101,8 +102,9 @@ class AttachmentLoadingTests { fun `test that attachments retrieved over the network are not used for code`() { withoutTestSerialization { driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), cordappsForAllNodes = emptySet())) { - val bankA = startNode(providedName = bankAName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow() - val bankB = startNode(providedName = bankBName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow() + val additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated") + val bankA = startNode(NodeParameters(providedName = bankAName, additionalCordapps = additionalCordapps)).getOrThrow() + val bankB = startNode(NodeParameters(providedName = bankBName, additionalCordapps = additionalCordapps)).getOrThrow() assertFailsWith("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") { bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 121898a830..3b6aa454cb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -5,7 +5,6 @@ package net.corda.testing.driver import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.flows.FlowLogic -import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NetworkParameters @@ -13,7 +12,6 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow -import net.corda.node.internal.Node import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.driver.internal.incrementalPortAllocation @@ -122,181 +120,6 @@ abstract class PortAllocation { } } -/** - * Helper builder for configuring a [Node] from Java. - * - * @property providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something - * random. Note that this must be unique as the driver uses it as a primary key! - * @property rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with - * all permissions. - * @property verifierType The type of transaction verifier to use. See: [VerifierType] - * @property customOverrides A map of custom node configuration overrides. - * @property startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @property maximumHeapSize The maximum JVM heap size to use for the node. - * @property logLevel Logging level threshold. - * @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. - * @property regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. - */ -@Suppress("unused") -data class NodeParameters( - val providedName: CordaX500Name? = null, - val rpcUsers: List = emptyList(), - val verifierType: VerifierType = VerifierType.InMemory, - val customOverrides: Map = emptyMap(), - val startInSameProcess: Boolean? = null, - val maximumHeapSize: String = "512m", - val logLevel: String? = null, - val additionalCordapps: Collection = emptySet(), - val regenerateCordappsOnStart: Boolean = false, - val flowOverrides: Map>, Class>> = emptyMap() -) { - /** - * Helper builder for configuring a [Node] from Java. - * - * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something - * random. Note that this must be unique as the driver uses it as a primary key! - * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with - * all permissions. - * @param verifierType The type of transaction verifier to use. See: [VerifierType] - * @param customOverrides A map of custom node configuration overrides. - * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @param maximumHeapSize The maximum JVM heap size to use for the node. - * @param logLevel Logging level threshold. - */ - constructor( - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String, - logLevel: String? = null - ) : this( - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - logLevel, - additionalCordapps = emptySet(), - regenerateCordappsOnStart = false - ) - - /** - * Helper builder for configuring a [Node] from Java. - * - * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something - * random. Note that this must be unique as the driver uses it as a primary key! - * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with - * all permissions. - * @param verifierType The type of transaction verifier to use. See: [VerifierType] - * @param customOverrides A map of custom node configuration overrides. - * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @param maximumHeapSize The maximum JVM heap size to use for the node. - */ - constructor( - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String - ) : this( - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - null, - additionalCordapps = emptySet(), - regenerateCordappsOnStart = false) - - /** - * Helper builder for configuring a [Node] from Java. - * - * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something - * random. Note that this must be unique as the driver uses it as a primary key! - * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with - * all permissions. - * @param verifierType The type of transaction verifier to use. See: [VerifierType] - * @param customOverrides A map of custom node configuration overrides. - * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @param maximumHeapSize The maximum JVM heap size to use for the node. - * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. - * @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. - */ - constructor( - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String, - additionalCordapps: Set = emptySet(), - regenerateCordappsOnStart: Boolean = false - ) : this( - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - null, - additionalCordapps, - regenerateCordappsOnStart) - - fun copy( - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String - ) = this.copy( - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - null) - - fun copy( - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String, - logLevel: String? - ) = this.copy( - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - logLevel, - additionalCordapps = additionalCordapps, - regenerateCordappsOnStart = regenerateCordappsOnStart) - - fun withProvidedName(providedName: CordaX500Name?): NodeParameters = copy(providedName = providedName) - fun withRpcUsers(rpcUsers: List): NodeParameters = copy(rpcUsers = rpcUsers) - fun withVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType) - fun withCustomOverrides(customOverrides: Map): NodeParameters = copy(customOverrides = customOverrides) - fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) - fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) - fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel) - fun withAdditionalCordapps(additionalCordapps: Set): NodeParameters = copy(additionalCordapps = additionalCordapps) - fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart) -} - /** * [driver] allows one to start up nodes like this: * driver { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt index 6eb2e7a71a..19e4271055 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -2,14 +2,11 @@ package net.corda.testing.driver import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture -import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.concurrent.map -import net.corda.node.internal.Node -import net.corda.testing.node.TestCordapp -import net.corda.testing.node.User import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User import java.nio.file.Path enum class VerifierType { @@ -56,11 +53,32 @@ interface DriverDSL { } } + /** + * Start a node using the default values of [NodeParameters]. + * + * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and + * it sees all previously started nodes, including the notaries. + */ + fun startNode(): CordaFuture = startNode(NodeParameters()) + + /** + * Start a node using the parameter values of the given [NodeParameters]. + * + * @param parameters The node parameters. + * + * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and + * it sees all previously started nodes, including the notaries. + */ + fun startNode(parameters: NodeParameters): CordaFuture + /** * Start a node. * - * @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style - * when called from Java code. + * NOTE: This method does not provide all the node parameters that are available and only exists for backwards compatibility. It is + * recommended you use [NodeParameters]. + * + * @param defaultParameters The default parameters for the node. If any of the remaining parameters to this method are specified then + * their values are taken instead of the corresponding value in [defaultParameters]. * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something * random. Note that this must be unique as the driver uses it as a primary key! * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. @@ -82,48 +100,16 @@ interface DriverDSL { customOverrides: Map = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, maximumHeapSize: String = defaultParameters.maximumHeapSize - ): CordaFuture - - /** - * Start a node. - * - * @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style - * when called from Java code. - * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something - * random. Note that this must be unique as the driver uses it as a primary key! - * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. - * @param verifierType The type of transaction verifier to use. See: [VerifierType]. - * @param customOverrides A map of custom node configuration overrides. - * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted - * as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate - * megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "512m" = 512 megabytes. - * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. - * @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. - * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and - * it sees all previously started nodes, including the notaries. - */ - fun startNode( - defaultParameters: NodeParameters = NodeParameters(), - providedName: CordaX500Name? = defaultParameters.providedName, - rpcUsers: List = defaultParameters.rpcUsers, - verifierType: VerifierType = defaultParameters.verifierType, - customOverrides: Map = defaultParameters.customOverrides, - startInSameProcess: Boolean? = defaultParameters.startInSameProcess, - maximumHeapSize: String = defaultParameters.maximumHeapSize, - additionalCordapps: Collection = defaultParameters.additionalCordapps, - regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart, - flowOverrides: Map>, Class>> = defaultParameters.flowOverrides - ): CordaFuture - - /** - * Helper function for starting a [Node] with custom parameters from Java. - * - * @param parameters The default parameters for the driver. - * @return [NodeHandle] that will be available sometime in the future. - */ - fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters) + ): CordaFuture { + return startNode(defaultParameters.copy( + providedName = providedName, + rpcUsers = rpcUsers, + verifierType = verifierType, + customOverrides = customOverrides, + startInSameProcess = startInSameProcess, + maximumHeapSize = maximumHeapSize + )) + } /** Call [startWebserver] with a default maximumHeapSize. */ @Suppress("DEPRECATION") diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt new file mode 100644 index 0000000000..baeecec398 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt @@ -0,0 +1,86 @@ +package net.corda.testing.driver + +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.testing.node.TestCordapp +import net.corda.testing.node.User + +/** + * Parameters for creating a node for [DriverDSL.startNode]. + * + * @property providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something + * random. Note that this must be unique as the driver uses it as a primary key! + * @property rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with + * all permissions. + * @property verifierType The type of transaction verifier to use. See: [VerifierType] + * @property customOverrides A map of custom node configuration overrides. + * @property startInSameProcess Determines if the node should be started inside the same process the Driver is running + * in. If null the Driver-level value will be used. + * @property maximumHeapSize The maximum JVM heap size to use for the node. Defaults to 512 MB. + * @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes + * managed by the [DriverDSL]. + * @property regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping + * and restarting the same node. + */ +@Suppress("unused") +data class NodeParameters( + val providedName: CordaX500Name? = null, + val rpcUsers: List = emptyList(), + val verifierType: VerifierType = VerifierType.InMemory, + val customOverrides: Map = emptyMap(), + val startInSameProcess: Boolean? = null, + val maximumHeapSize: String = "512m", + val additionalCordapps: Collection = emptySet(), + val regenerateCordappsOnStart: Boolean = false, + val flowOverrides: Map>, Class>> = emptyMap() +) { + /** + * Create a new node parameters object with default values. Each parameter can be specified with its wither method which returns a copy + * with that value. + */ + constructor() : this(providedName = null) + + fun withProvidedName(providedName: CordaX500Name?): NodeParameters = copy(providedName = providedName) + fun withRpcUsers(rpcUsers: List): NodeParameters = copy(rpcUsers = rpcUsers) + fun withVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType) + fun withCustomOverrides(customOverrides: Map): NodeParameters = copy(customOverrides = customOverrides) + fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) + fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) + fun withAdditionalCordapps(additionalCordapps: Set): NodeParameters = copy(additionalCordapps = additionalCordapps) + fun withRegenerateCordappsOnStart(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart) + fun withFlowOverrides(flowOverrides: Map>, Class>>): NodeParameters = copy(flowOverrides = flowOverrides) + + constructor( + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String + ) : this( + providedName, + rpcUsers, + verifierType, + customOverrides, + startInSameProcess, + maximumHeapSize, + additionalCordapps = emptySet()) + + fun copy( + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String + ) = this.copy( + providedName = providedName, + rpcUsers = rpcUsers, + verifierType = verifierType, + customOverrides = customOverrides, + startInSameProcess = startInSameProcess, + maximumHeapSize = maximumHeapSize, + additionalCordapps = additionalCordapps + ) +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 5028da86ce..6326319f8d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -9,7 +9,6 @@ import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader import net.corda.cliutils.CommonCliConstants.BASE_DIR import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf -import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* import net.corda.core.internal.concurrent.* @@ -28,6 +27,7 @@ import net.corda.node.internal.NodeWithInfo import net.corda.node.internal.clientSslOptionsCompatibleWith import net.corda.node.services.Permissions import net.corda.node.services.config.* +import net.corda.node.services.config.NodeConfiguration.Companion.cordappDirectoriesKey import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NodeRegistrationHelper import net.corda.nodeapi.internal.DevIdentityGenerator @@ -43,7 +43,6 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.driver.* -import net.corda.testing.driver.VerifierType import net.corda.testing.driver.internal.InProcessImpl import net.corda.testing.driver.internal.NodeHandleInternal import net.corda.testing.driver.internal.OutOfProcessImpl @@ -52,7 +51,6 @@ import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec import net.corda.testing.node.TestCordapp -import net.corda.testing.node.User import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import okhttp3.OkHttpClient import okhttp3.Request @@ -186,46 +184,14 @@ class DriverDSLImpl( } } - override fun startNode(defaultParameters: NodeParameters, - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String): CordaFuture { - return startNode( - defaultParameters, - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - defaultParameters.additionalCordapps, - defaultParameters.regenerateCordappsOnStart - ) - } - - override fun startNode( - defaultParameters: NodeParameters, - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String, - additionalCordapps: Collection, - regenerateCordappsOnStart: Boolean, - flowOverrides: Map>, Class>> - ): CordaFuture { + override fun startNode(parameters: NodeParameters): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name - val name = providedName - ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB") + val name = parameters.providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB") val registrationFuture = if (compatibilityZone?.rootCert != null) { // We don't need the network map to be available to be able to register the node - startNodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.config(), customOverrides) + startNodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.config(), parameters.customOverrides) } else { doneFuture(Unit) } @@ -233,27 +199,20 @@ class DriverDSLImpl( return registrationFuture.flatMap { networkMapAvailability.flatMap { // But starting the node proper does require the network map - startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress, additionalCordapps, regenerateCordappsOnStart, flowOverrides, signCordapps) + startRegisteredNode(name, it, parameters, p2pAddress, signCordapps) } } } private fun startRegisteredNode(name: CordaX500Name, localNetworkMap: LocalNetworkMap?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean? = null, - maximumHeapSize: String = "512m", + parameters: NodeParameters, p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), - additionalCordapps: Collection = emptySet(), - regenerateCordappsOnStart: Boolean = false, - flowOverrides: Map>, Class>> = emptyMap(), signCordapps: Boolean = false): CordaFuture { val rpcAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort() - val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } + val users = parameters.rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } val czUrlConfig = when (compatibilityZone) { null -> emptyMap() is SharedCompatibilityZoneParams -> @@ -270,7 +229,8 @@ class DriverDSLImpl( emptyMap() } - val flowOverrideConfig = FlowOverrideConfig(flowOverrides.entries.map { FlowOverride(it.key.canonicalName, it.value.canonicalName) }) + val flowOverrideConfig = FlowOverrideConfig(parameters.flowOverrides.map { FlowOverride(it.key.canonicalName, it.value.canonicalName) }) + val overrides = configOf( NodeConfiguration::myLegalName.name to name.toString(), NodeConfiguration::p2pAddress.name to p2pAddress.toString(), @@ -278,15 +238,15 @@ class DriverDSLImpl( "rpcSettings.adminAddress" to rpcAdminAddress.toString(), NodeConfiguration::useTestClock.name to useTestClock, NodeConfiguration::rpcUsers.name to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, - NodeConfiguration::verifierType.name to verifierType.name, + NodeConfiguration::verifierType.name to parameters.verifierType.name, NodeConfiguration::flowOverrides.name to flowOverrideConfig.toConfig().root().unwrapped() - ) + czUrlConfig + jmxConfig + customOverrides + ) + czUrlConfig + jmxConfig + parameters.customOverrides val config = NodeConfig(ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) )).checkAndOverrideForInMemoryDB() - return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap, additionalCordapps, regenerateCordappsOnStart, signCordapps) + return startNodeInternal(config, webAddress, localNetworkMap, parameters, signCordapps) } private fun startNodeRegistration( @@ -503,9 +463,7 @@ class DriverDSLImpl( return startRegisteredNode( spec.name, localNetworkMap, - spec.rpcUsers, - spec.verifierType, - customOverrides = notaryConfig + customOverrides + NodeParameters(rpcUsers = spec.rpcUsers, verifierType = spec.verifierType, customOverrides = notaryConfig + customOverrides) ).map { listOf(it) } } @@ -531,9 +489,8 @@ class DriverDSLImpl( val firstNodeFuture = startRegisteredNode( nodeNames[0], localNetworkMap, - spec.rpcUsers, - spec.verifierType, - customOverrides = notaryConfig(clusterAddress)) + NodeParameters(rpcUsers = spec.rpcUsers, verifierType = spec.verifierType, customOverrides = notaryConfig(clusterAddress)) + ) // All other nodes will join the cluster val restNodeFutures = nodeNames.drop(1).map { @@ -541,9 +498,7 @@ class DriverDSLImpl( startRegisteredNode( it, localNetworkMap, - spec.rpcUsers, - spec.verifierType, - customOverrides = notaryConfig(nodeAddress, clusterAddress) + NodeParameters(rpcUsers = spec.rpcUsers, verifierType = spec.verifierType, customOverrides = notaryConfig(nodeAddress, clusterAddress)) ) } @@ -579,11 +534,8 @@ class DriverDSLImpl( private fun startNodeInternal(specifiedConfig: NodeConfig, webAddress: NetworkHostAndPort, - startInProcess: Boolean?, - maximumHeapSize: String, localNetworkMap: LocalNetworkMap?, - additionalCordapps: Collection, - regenerateCordappsOnStart: Boolean = false, + parameters: NodeParameters, signCordapps: Boolean = false): CordaFuture { val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories() @@ -597,24 +549,24 @@ class DriverDSLImpl( val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } - val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) { + val existingCorDappDirectories = if (parameters.regenerateCordappsOnStart) { emptyList() - } else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) { - specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) + } else if (specifiedConfig.typesafe.hasPath(cordappDirectoriesKey)) { + specifiedConfig.typesafe.getStringList(cordappDirectoriesKey) } else { emptyList() } // Instead of using cordappsForAllNodes we get only these that are missing from additionalCordapps // This way we prevent errors when we want the same CordApp but with different config - val appOverrides = additionalCordapps.map { it.name to it.version}.toSet() + val appOverrides = parameters.additionalCordapps.map { it.name to it.version }.toSet() val baseCordapps = cordappsForAllNodes.filter { !appOverrides.contains(it.name to it.version) } - val cordappDirectories = existingCorDappDirectoriesOption + (baseCordapps + additionalCordapps).map { TestCordappDirectories.getJarDirectory(it, signJar = signCordapps).toString() } + val cordappDirectories = existingCorDappDirectories + (baseCordapps + parameters.additionalCordapps).map { TestCordappDirectories.getJarDirectory(it, signJar = signCordapps).toString() } - val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))) + val config = NodeConfig(specifiedConfig.typesafe.withValue(cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))) - if (startInProcess ?: startNodesInProcess) { + if (parameters.startInSameProcess ?: startNodesInProcess) { val nodeAndThreadFuture = startInProcessNode(executorService, config) shutdownManager.registerShutdown( nodeAndThreadFuture.map { (node, thread) -> @@ -641,7 +593,7 @@ class DriverDSLImpl( return nodeFuture } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = startOutOfProcessNode(config, quasarJarPath, debugPort, systemProperties, maximumHeapSize) + val process = startOutOfProcessNode(config, quasarJarPath, debugPort, systemProperties, parameters.maximumHeapSize) // Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is // true because we don't want orphaned processes in the case that the parent process is terminated by the @@ -1193,14 +1145,15 @@ internal fun DriverParameters.cordappsForAllNodes(): Collection = c ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture { - var customOverrides = emptyMap() - if (!devMode) { + val customOverrides = if (!devMode) { val nodeDir = baseDirectory(providedName) val certificatesDirectory = nodeDir / "certificates" val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) p2pSslConfig.configureDevKeyAndTrustStores(providedName, signingCertStore, certificatesDirectory) - customOverrides = mapOf("devMode" to "false") + parameters.customOverrides + mapOf("devMode" to "false") + } else { + parameters.customOverrides } - return startNode(parameters, providedName = providedName, customOverrides = customOverrides) + return startNode(parameters.copy(providedName = providedName, customOverrides = customOverrides)) } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 268f1c7d4b..30fdd91d9f 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -80,16 +80,16 @@ class ExplorerSimulation(private val options: OptionSet) { val bob = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)) val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB") val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US") - val issuerGBP = startNode( + val issuerGBP = startNode(NodeParameters( providedName = ukBankName, rpcUsers = listOf(manager), additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("GBP")))) - ) - val issuerUSD = startNode( + )) + val issuerUSD = startNode(NodeParameters( providedName = usaBankName, rpcUsers = listOf(manager), additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("USD")))) - ) + )) notaryNode = defaultNotaryNode.get() aliceNode = alice.get() From 2818737f12fffec0894f54854bba08adfbbcc273 Mon Sep 17 00:00:00 2001 From: Dominic Fox <40790090+distributedleetravis@users.noreply.github.com> Date: Wed, 28 Nov 2018 15:21:43 +0000 Subject: [PATCH 2/3] Fix comment (#4308) --- .../corda/core/contracts/TransactionVerificationException.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt index 18531dfe67..b7cd7fe629 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -149,7 +149,7 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S "is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " + "a full cycle. Offending indices $nonMatching", null) - /**HEAD + /** * All encumbered states should be assigned to the same notary. This is due to the fact that multi-notary * transactions are not supported and thus two encumbered states with different notaries cannot be consumed * in the same transaction. From 68145e16de2cbcd4b9268259992912953f88d6a3 Mon Sep 17 00:00:00 2001 From: Dominic Fox <40790090+distributedleetravis@users.noreply.github.com> Date: Wed, 28 Nov 2018 16:39:54 +0000 Subject: [PATCH 3/3] CORDA-2255 suppress warnings on read-only properties (#4307) * CORDA-2255 suppress warnings on read-only properties * Avoid creating copies of LocalTypeInformationBuilder * Disambiguate complex elvis expression --- .../model/LocalTypeInformationBuilder.kt | 87 +++++++++++++------ 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt index b99abb9bda..986742a7d7 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt @@ -33,22 +33,43 @@ import kotlin.reflect.jvm.javaType * will find it useful to revert to earlier states of knowledge about which types have been visited on a given branch. */ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, - val resolutionContext: Type? = null, - val visited: Set = emptySet(), - val cycles: MutableList = mutableListOf()) { + var resolutionContext: Type? = null, + var visited: Set = emptySet(), + val cycles: MutableList = mutableListOf(), + var warnIfNonComposable: Boolean = true) { companion object { private val logger = contextLogger() } + /** + * If we are examining the type of a read-only property, or a type flagged as [Opaque], then we do not need to warn + * if the [LocalTypeInformation] for that type (or any of its related types) is [LocalTypeInformation.NonComposable]. + */ + private inline fun suppressWarningsAnd(block: LocalTypeInformationBuilder.() -> T): T { + val previous = warnIfNonComposable + return try { + warnIfNonComposable = false + block() + } finally { + warnIfNonComposable = previous + } + } + /** * Recursively build [LocalTypeInformation] for the given [Type] and [TypeIdentifier] */ fun build(type: Type, typeIdentifier: TypeIdentifier): LocalTypeInformation = - if (typeIdentifier in visited) LocalTypeInformation.Cycle(type, typeIdentifier).apply { cycles.add(this) } - else lookup.findOrBuild(type, typeIdentifier) { isOpaque -> - copy(visited = visited + typeIdentifier).buildIfNotFound(type, typeIdentifier, isOpaque) - } + if (typeIdentifier in visited) LocalTypeInformation.Cycle(type, typeIdentifier).apply { cycles.add(this) } + else lookup.findOrBuild(type, typeIdentifier) { isOpaque -> + val previous = visited + try { + visited = visited + typeIdentifier + buildIfNotFound(type, typeIdentifier, isOpaque) + } finally { + visited = previous + } + } private fun resolveAndBuild(type: Type): LocalTypeInformation { val resolved = type.resolveAgainstContext() @@ -57,7 +78,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, } private fun Type.resolveAgainstContext(): Type = - if (resolutionContext == null) this else resolveAgainst(resolutionContext) + resolutionContext?.let(::resolveAgainst) ?: this private fun buildIfNotFound(type: Type, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation { val rawType = type.asClass() @@ -79,7 +100,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, private fun buildForClass(type: Class<*>, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation = withContext(type) { when { Collection::class.java.isAssignableFrom(type) && - !EnumSet::class.java.isAssignableFrom(type) -> LocalTypeInformation.ACollection(type, typeIdentifier, LocalTypeInformation.Unknown) + !EnumSet::class.java.isAssignableFrom(type) -> LocalTypeInformation.ACollection(type, typeIdentifier, LocalTypeInformation.Unknown) Map::class.java.isAssignableFrom(type) -> LocalTypeInformation.AMap(type, typeIdentifier, LocalTypeInformation.Unknown, LocalTypeInformation.Unknown) type.kotlin.javaPrimitiveType != null -> LocalTypeInformation.Atomic(type.kotlin.javaPrimitiveType!!, typeIdentifier) type.isEnum -> LocalTypeInformation.AnEnum( @@ -95,9 +116,12 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, type.isInterface -> buildInterface(type, typeIdentifier, emptyList()) type.isAbstractClass -> buildAbstract(type, typeIdentifier, emptyList()) isOpaque -> LocalTypeInformation.Opaque( - type, - typeIdentifier, - buildNonAtomic(type, type, typeIdentifier, emptyList(), true)) + type, + typeIdentifier, + suppressWarningsAnd { buildNonAtomic(type, type, typeIdentifier, emptyList()) }) + Exception::class.java.isAssignableFrom(type.asClass()) -> suppressWarningsAnd { + buildNonAtomic(type, type, typeIdentifier, emptyList()) + } else -> buildNonAtomic(type, type, typeIdentifier, emptyList()) } } @@ -109,7 +133,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, isOpaque: Boolean): LocalTypeInformation = withContext(type) { when { Collection::class.java.isAssignableFrom(rawType) && - !EnumSet::class.java.isAssignableFrom(rawType) -> + !EnumSet::class.java.isAssignableFrom(rawType) -> LocalTypeInformation.ACollection(type, typeIdentifier, buildTypeParameterInformation(type)[0]) Map::class.java.isAssignableFrom(rawType) -> { val (keyType, valueType) = buildTypeParameterInformation(type) @@ -118,8 +142,8 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, rawType.isInterface -> buildInterface(type, typeIdentifier, buildTypeParameterInformation(type)) rawType.isAbstractClass -> buildAbstract(type, typeIdentifier, buildTypeParameterInformation(type)) isOpaque -> LocalTypeInformation.Opaque(rawType, - typeIdentifier, - buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type), true)) + typeIdentifier, + suppressWarningsAnd { buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) }) else -> buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) } } @@ -143,8 +167,15 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, buildInterfaceInformation(type), typeParameters) - private inline fun withContext(newContext: Type, block: LocalTypeInformationBuilder.() -> T): T = - copy(resolutionContext = newContext).run(block) + private inline fun withContext(newContext: Type, block: LocalTypeInformationBuilder.() -> T): T { + val previous = resolutionContext + return try { + resolutionContext = newContext + block() + } finally { + resolutionContext = previous + } + } /** * Build a non-atomic type, which is either [Composable] or [NonComposable]. @@ -156,13 +187,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, * Rather than throwing an exception if a type is [NonComposable], we capture its type information so that it can * still be used to _serialize_ values, or as the basis for deciding on an evolution strategy. */ - private fun buildNonAtomic(rawType: Class<*>, type: Type, typeIdentifier: TypeIdentifier, typeParameterInformation: List, suppressWarning: Boolean = false): LocalTypeInformation { + private fun buildNonAtomic(rawType: Class<*>, type: Type, typeIdentifier: TypeIdentifier, typeParameterInformation: List): LocalTypeInformation { val superclassInformation = buildSuperclassInformation(type) val interfaceInformation = buildInterfaceInformation(type) val observedConstructor = constructorForDeserialization(type) if (observedConstructor == null) { - if (!suppressWarning) { + if (warnIfNonComposable) { logger.info("No unique deserialisation constructor found for class $rawType, type is marked as non-composable") } return LocalTypeInformation.NonComposable(type, typeIdentifier, null, buildReadOnlyProperties(rawType), @@ -175,7 +206,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, val hasNonComposableProperties = properties.values.any { it.type is LocalTypeInformation.NonComposable } if (!propertiesSatisfyConstructor(constructorInformation, properties) || hasNonComposableProperties) { - if (!suppressWarning) { + if (warnIfNonComposable) { if (hasNonComposableProperties) { logger.info("Type ${type.typeName} has non-composable properties and has been marked as non-composable") } else { @@ -214,7 +245,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, } private fun buildSuperclassInformation(type: Type): LocalTypeInformation = - resolveAndBuild(type.asClass().genericSuperclass) + resolveAndBuild(type.asClass().genericSuperclass) private fun buildInterfaceInformation(type: Type) = type.allInterfaces.asSequence().mapNotNull { @@ -244,8 +275,10 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, if (descriptor.field == null || descriptor.getter == null) null else { val paramType = (descriptor.getter.genericReturnType).resolveAgainstContext() - val paramTypeInformation = build(paramType, TypeIdentifier.forGenericType(paramType, resolutionContext - ?: rawType)) + // Because this parameter is read-only, we don't need to warn if its type is non-composable. + val paramTypeInformation = suppressWarningsAnd { + build(paramType, TypeIdentifier.forGenericType(paramType, resolutionContext ?: rawType)) + } val isMandatory = paramType.asClass().isPrimitive || !descriptor.getter.returnsNullable() name to LocalPropertyInformation.ReadOnlyProperty(descriptor.getter, paramTypeInformation, isMandatory) } @@ -275,10 +308,10 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, name: String, descriptor: PropertyDescriptor, constructorInformation: LocalConstructorInformation): LocalPropertyInformation? { - val constructorIndex = constructorParameterIndices[name] ?: - // In some very rare cases we have a constructor parameter matched by a getter with no backing field, - // and cannot infer whether the property name should be capitalised or not. - constructorParameterIndices[name.decapitalize()] ?: return null + // In some very rare cases we have a constructor parameter matched by a getter with no backing field, + // and cannot infer whether the property name should be capitalised or not. + val constructorIndex = constructorParameterIndices[name] ?: constructorParameterIndices[name.decapitalize()] + if (constructorIndex == null) return null if (descriptor.getter == null) { if (descriptor.field == null) return null