From f1ebaa761b6f4ca2538e67e62779ba5d426c9e46 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Fri, 27 Mar 2020 10:01:30 +0000 Subject: [PATCH 01/10] CORDA-3680: Add CorDapp custom serialisers to Driver's in-process nodes. (#6098) * Run serialisation tests with both in-process and out-of-process nodes. * Add custom serialisers and whitelists to Driver's AMQPServerSerializationScheme. --- .../amqp/AMQPClientSerializationScheme.kt | 3 +-- .../node/ContractWithCustomSerializerTest.kt | 12 +++++++++-- ...ContractWithMissingCustomSerializerTest.kt | 20 +++++++++++++------ .../amqp/AMQPServerSerializationScheme.kt | 12 ++++++++--- .../InternalSerializationTestHelpers.kt | 10 ++++++---- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt index fc36e59ffd..d61f13dd4c 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt @@ -8,7 +8,6 @@ import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal._rpcClientSerializationEnv -import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.serialization.internal.* import net.corda.serialization.internal.amqp.* import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer @@ -57,7 +56,7 @@ class AMQPClientSerializationScheme( } } - override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: UseCase): Boolean { return magic == amqpMagic && (target == UseCase.RPCClient || target == UseCase.P2P) } diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt index d55787148f..ffb2d297b1 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt @@ -18,13 +18,21 @@ import net.corda.testing.node.internal.cordappWithPackages import org.assertj.core.api.Assertions.assertThat import org.junit.BeforeClass import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters import kotlin.test.assertFailsWith +@RunWith(Parameterized::class) @Suppress("FunctionName") -class ContractWithCustomSerializerTest { +class ContractWithCustomSerializerTest(private val runInProcess: Boolean) { companion object { const val CURRANTS = 5000L + @Parameters + @JvmStatic + fun modes(): List> = listOf(Array(1) { true }, Array(1) { false }) + @BeforeClass @JvmStatic fun checkData() { @@ -37,7 +45,7 @@ class ContractWithCustomSerializerTest { val user = User("u", "p", setOf(Permissions.all())) driver(DriverParameters( portAllocation = incrementalPortAllocation(), - startNodesInProcess = false, + startNodesInProcess = runInProcess, notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), cordappsForAllNodes = listOf( cordappWithPackages("net.corda.flows.serialization.custom").signed(), diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt index 0c4d5984fe..2110ff3cfe 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt @@ -26,10 +26,14 @@ import net.corda.testing.node.internal.cordappWithPackages import org.assertj.core.api.Assertions.assertThat import org.junit.BeforeClass import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters import kotlin.test.assertFailsWith +@RunWith(Parameterized::class) @Suppress("FunctionName") -class ContractWithMissingCustomSerializerTest { +class ContractWithMissingCustomSerializerTest(private val runInProcess: Boolean) { companion object { const val BOBBINS = 5000L @@ -37,15 +41,19 @@ class ContractWithMissingCustomSerializerTest { val flowCorDapp = cordappWithPackages("net.corda.flows.serialization.missing").signed() val contractCorDapp = cordappWithPackages("net.corda.contracts.serialization.missing").signed() - fun driverParameters(cordapps: List): DriverParameters { + fun driverParameters(cordapps: List, runInProcess: Boolean): DriverParameters { return DriverParameters( portAllocation = incrementalPortAllocation(), - startNodesInProcess = false, + startNodesInProcess = runInProcess, notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), cordappsForAllNodes = cordapps ) } + @Parameters + @JvmStatic + fun modes(): List> = listOf(Array(1) { true }, Array(1) { false }) + @BeforeClass @JvmStatic fun checkData() { @@ -62,7 +70,7 @@ class ContractWithMissingCustomSerializerTest { val flowId = flowCorDapp.jarFile.hash val fixupCorDapp = cordappWithFixups(listOf(setOf(contractId) to setOf(contractId, flowId))).signed() - driver(driverParameters(listOf(flowCorDapp, contractCorDapp, fixupCorDapp))) { + driver(driverParameters(listOf(flowCorDapp, contractCorDapp, fixupCorDapp), runInProcess)) { val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val ex = assertFailsWith { CordaRPCClient(hostAndPort = alice.rpcAddress) @@ -83,7 +91,7 @@ class ContractWithMissingCustomSerializerTest { */ @Test(timeout=300_000) fun `flow with missing custom serializer but without fixup`() { - driver(driverParameters(listOf(flowCorDapp, contractCorDapp))) { + driver(driverParameters(listOf(flowCorDapp, contractCorDapp), runInProcess)) { val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val ex = assertFailsWith { CordaRPCClient(hostAndPort = alice.rpcAddress) @@ -104,7 +112,7 @@ class ContractWithMissingCustomSerializerTest { */ @Test(timeout=300_000) fun `transaction builder flow with missing custom serializer by rpc`() { - driver(driverParameters(listOf(flowCorDapp, contractCorDapp))) { + driver(driverParameters(listOf(flowCorDapp, contractCorDapp), runInProcess)) { val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val ex = assertFailsWith { CordaRPCClient(hostAndPort = alice.rpcAddress) diff --git a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt index ab801c9cde..48bbe55ea1 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt @@ -18,10 +18,16 @@ class AMQPServerSerializationScheme( cordappSerializationWhitelists: Set, serializerFactoriesForContexts: MutableMap ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) { - constructor(cordapps: List) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap(128).toSynchronised()) - constructor(cordapps: List, serializerFactoriesForContexts: MutableMap) : this(cordapps.customSerializers, cordapps.serializationWhitelists, serializerFactoriesForContexts) + constructor(cordapps: List) : this(cordapps.customSerializers, cordapps.serializationWhitelists) + constructor(cordapps: List, serializerFactoriesForContexts: MutableMap) + : this(cordapps.customSerializers, cordapps.serializationWhitelists, serializerFactoriesForContexts) + constructor( + cordappCustomSerializers: Set>, + cordappSerializationWhitelists: Set + ) : this(cordappCustomSerializers, cordappSerializationWhitelists, AccessOrderLinkedHashMap(128).toSynchronised()) - constructor() : this(emptySet(), emptySet(), AccessOrderLinkedHashMap(128).toSynchronised() ) + @Suppress("UNUSED") + constructor() : this(emptySet(), emptySet()) override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt index a9572f62fd..f7a07f33bf 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt @@ -21,16 +21,18 @@ fun createTestSerializationEnv(): SerializationEnvironment { } fun createTestSerializationEnv(classLoader: ClassLoader?): SerializationEnvironment { - val clientSerializationScheme = if (classLoader != null) { + val (clientSerializationScheme, serverSerializationScheme) = if (classLoader != null) { val customSerializers = createInstancesOfClassesImplementing(classLoader, SerializationCustomSerializer::class.java) val serializationWhitelists = ServiceLoader.load(SerializationWhitelist::class.java, classLoader).toSet() - AMQPClientSerializationScheme(customSerializers, serializationWhitelists) + + Pair(AMQPClientSerializationScheme(customSerializers, serializationWhitelists), + AMQPServerSerializationScheme(customSerializers, serializationWhitelists)) } else { - AMQPClientSerializationScheme(emptyList()) + Pair(AMQPClientSerializationScheme(emptyList()), AMQPServerSerializationScheme(emptyList())) } val factory = SerializationFactoryImpl().apply { registerScheme(clientSerializationScheme) - registerScheme(AMQPServerSerializationScheme(emptyList())) + registerScheme(serverSerializationScheme) } return SerializationEnvironment.with( factory, From b340766506b1b4d24cb5fe64e7f1f2df07701ee1 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 2 Apr 2020 17:43:51 +0100 Subject: [PATCH 02/10] ENT-5039 Improved help text for commands (#6006) (#6122) * Improved help text for commands * Address feedback Co-authored-by: jakubbielawa --- build.gradle | 2 +- .../net/corda/tools/shell/AttachmentShellCommand.java | 2 ++ .../net/corda/tools/shell/CheckpointShellCommand.java | 2 ++ .../main/java/net/corda/tools/shell/FlowShellCommand.java | 8 ++++++-- .../net/corda/tools/shell/HashLookupShellCommand.java | 6 +++++- .../java/net/corda/tools/shell/OutputFormatCommand.java | 1 + 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 0772c6740a..85fc16a9da 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ buildscript { ext.dependency_checker_version = '5.2.0' ext.commons_collections_version = '4.3' ext.beanutils_version = '1.9.3' - ext.crash_version = '1.7.2' + ext.crash_version = '1.7.4' ext.jsr305_version = constants.getProperty("jsr305Version") ext.shiro_version = '1.4.1' ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') diff --git a/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java index 1e58eb1c9c..ec075e38fb 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java @@ -2,6 +2,7 @@ package net.corda.tools.shell; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Usage; import static net.corda.tools.shell.InteractiveShell.runAttachmentTrustInfoView; @@ -9,6 +10,7 @@ public class AttachmentShellCommand extends InteractiveShellCommand { @Command @Man("Displays the trusted CorDapp attachments that have been manually installed or received over the network") + @Usage("Displays the trusted CorDapp attachments that have been manually installed or received over the network") public void trustInfo() { runAttachmentTrustInfoView(out, ops()); } diff --git a/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java index 8c0f50666c..19b5e99a53 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java @@ -2,6 +2,7 @@ package net.corda.tools.shell; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Usage; import static net.corda.tools.shell.InteractiveShell.*; @@ -9,6 +10,7 @@ public class CheckpointShellCommand extends InteractiveShellCommand { @Command @Man("Outputs the contents of all checkpoints as json to be manually reviewed") + @Usage("Outputs the contents of all checkpoints as json to be manually reviewed") public void dump() { runDumpCheckpoints(ops()); } diff --git a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java index a99c767e54..d0120ed36f 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java @@ -31,7 +31,11 @@ public class FlowShellCommand extends InteractiveShellCommand { private static final Logger logger = LoggerFactory.getLogger(FlowShellCommand.class); @Command - @Usage("Start a (work)flow on the node. This is how you can change the ledger.") + @Usage("Start a (work)flow on the node. This is how you can change the ledger.\n\n" + + "\t\t Starting flow is the primary way in which you command the node to change the ledger.\n" + + "\t\t This command is generic, so the right way to use it depends on the flow you wish to start. You can use the 'flow start'\n" + + "\t\t command with either a full class name, or a substring of the class name that's unambiguous. The parameters to the\n" + + "\t\t flow constructors (the right one is picked automatically) are then specified using the same syntax as for the run command.\n") public void start( @Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input @@ -55,7 +59,7 @@ public class FlowShellCommand extends InteractiveShellCommand { ANSIProgressRenderer ansiProgressRenderer, ObjectMapper om) { if (name == null) { - out.println("You must pass a name for the flow, see 'man flow'", Decoration.bold, Color.red); + out.println("You must pass a name for the flow. Example: \"start Yo target: Some other company\"", Decoration.bold, Color.red); return; } String inp = input == null ? "" : String.join(" ", input).trim(); diff --git a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java index 53b7c147d4..79cf1ee273 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java @@ -28,7 +28,11 @@ public class HashLookupShellCommand extends InteractiveShellCommand { logger.info("Executing command \"hashLookup\"."); if (txIdHash == null) { - out.println("Please provide a hexadecimal transaction Id hash value, see 'man hashLookup'", Decoration.bold, Color.red); + out.println("Checks if a transaction matching a specified Id hash value is recorded on this node.\n\n" + + "This is mainly intended to be used for troubleshooting notarisation issues when a\n" + + "state is claimed to be already consumed by another transaction.\n\n" + + "Example usage: hashLookup E470FD8A6350A74217B0A99EA5FB71F091C84C64AD0DE0E72ECC10421D03AAC9"); + out.println("Please provide a hexadecimal transaction Id hash value", Decoration.bold, Color.red); return; } diff --git a/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java index 3b20da82f0..1a3fffb79e 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java @@ -15,6 +15,7 @@ import org.crsh.text.RenderPrintWriter; import java.util.Map; @Man("Allows you to see and update the format that's currently used for the commands' output.") +@Usage("Allows you to see and update the format that's currently used for the commands' output.") public class OutputFormatCommand extends InteractiveShellCommand { public OutputFormatCommand() {} From 032f008e7ebd468973f2639d942f7519f6d51add Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Fri, 3 Apr 2020 08:35:01 +0100 Subject: [PATCH 03/10] CORDA-3688: Apply @Named annotation to CRaSH commands to fix usage messages. (#6126) --- .../main/java/net/corda/tools/shell/AttachmentShellCommand.java | 2 ++ .../main/java/net/corda/tools/shell/CheckpointShellCommand.java | 2 ++ .../src/main/java/net/corda/tools/shell/FlowShellCommand.java | 1 + .../main/java/net/corda/tools/shell/HashLookupShellCommand.java | 2 ++ .../main/java/net/corda/tools/shell/OutputFormatCommand.java | 2 ++ .../src/main/java/net/corda/tools/shell/RunShellCommand.java | 2 ++ .../src/main/java/net/corda/tools/shell/StartShellCommand.java | 1 + 7 files changed, 12 insertions(+) diff --git a/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java index ec075e38fb..30cd727585 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java @@ -2,10 +2,12 @@ package net.corda.tools.shell; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Named; import org.crsh.cli.Usage; import static net.corda.tools.shell.InteractiveShell.runAttachmentTrustInfoView; +@Named("attachments") public class AttachmentShellCommand extends InteractiveShellCommand { @Command diff --git a/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java index 19b5e99a53..f0a26e61d0 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java @@ -2,10 +2,12 @@ package net.corda.tools.shell; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Named; import org.crsh.cli.Usage; import static net.corda.tools.shell.InteractiveShell.*; +@Named("checkpoints") public class CheckpointShellCommand extends InteractiveShellCommand { @Command diff --git a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java index d0120ed36f..3922b9a1cc 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java @@ -26,6 +26,7 @@ import static net.corda.tools.shell.InteractiveShell.runStateMachinesView; "command with either a full class name, or a substring of the class name that's unambiguous. The parameters to the \n" + "flow constructors (the right one is picked automatically) are then specified using the same syntax as for the run command." ) +@Named("flow") public class FlowShellCommand extends InteractiveShellCommand { private static final Logger logger = LoggerFactory.getLogger(FlowShellCommand.class); diff --git a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java index 79cf1ee273..831fc18b4c 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java @@ -6,6 +6,7 @@ import net.corda.core.messaging.StateMachineTransactionMapping; import org.crsh.cli.Argument; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Named; import org.crsh.cli.Usage; import org.crsh.text.Color; import org.crsh.text.Decoration; @@ -15,6 +16,7 @@ import org.slf4j.LoggerFactory; import java.util.List; import java.util.Optional; +@Named("hashLookup") public class HashLookupShellCommand extends InteractiveShellCommand { private static Logger logger = LoggerFactory.getLogger(HashLookupShellCommand.class); diff --git a/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java index 1a3fffb79e..5ec1937f3e 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java @@ -7,6 +7,7 @@ import net.corda.tools.shell.InteractiveShell.OutputFormat; import org.crsh.cli.Argument; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Named; import org.crsh.cli.Usage; import org.crsh.command.InvocationContext; import org.crsh.command.ScriptException; @@ -16,6 +17,7 @@ import java.util.Map; @Man("Allows you to see and update the format that's currently used for the commands' output.") @Usage("Allows you to see and update the format that's currently used for the commands' output.") +@Named("output-format") public class OutputFormatCommand extends InteractiveShellCommand { public OutputFormatCommand() {} diff --git a/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java index df75fba4fd..192ccc106a 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java @@ -7,6 +7,7 @@ import net.corda.core.messaging.CordaRPCOps; import org.crsh.cli.Argument; import org.crsh.cli.Command; import org.crsh.cli.Man; +import org.crsh.cli.Named; import org.crsh.cli.Usage; import org.crsh.command.InvocationContext; import org.jetbrains.annotations.NotNull; @@ -23,6 +24,7 @@ import static java.util.Comparator.comparing; // Note that this class cannot be converted to Kotlin because CRaSH does not understand InvocationContext> which // is the closest you can get in Kotlin to raw types. +@Named("run") public class RunShellCommand extends InteractiveShellCommand { private static final Logger logger = LoggerFactory.getLogger(RunShellCommand.class); diff --git a/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java index 8a1a1c47c6..a022e95af1 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java @@ -12,6 +12,7 @@ import java.util.*; import static java.util.stream.Collectors.joining; +@Named("start") public class StartShellCommand extends InteractiveShellCommand { private static Logger logger = LoggerFactory.getLogger(StartShellCommand.class); From 4210c0d81fcf6fb850b5338e1dc665f3d6aa7758 Mon Sep 17 00:00:00 2001 From: Razvan Codreanu <52859362+Schife@users.noreply.github.com> Date: Fri, 17 Apr 2020 15:52:47 +0100 Subject: [PATCH 04/10] INFRA-284 switching from local k8s label (#6157) --- .ci/dev/integration/Jenkinsfile | 2 +- .ci/dev/nightly-regression/Jenkinsfile | 2 +- .ci/dev/on-demand-tests/Jenkinsfile | 2 +- .ci/dev/regression/Jenkinsfile | 2 +- .ci/dev/smoke/Jenkinsfile | 2 +- .ci/dev/unit/Jenkinsfile | 2 +- Jenkinsfile | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.ci/dev/integration/Jenkinsfile b/.ci/dev/integration/Jenkinsfile index 420ad78d2a..eba467e5a7 100644 --- a/.ci/dev/integration/Jenkinsfile +++ b/.ci/dev/integration/Jenkinsfile @@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() timeout(time: 3, unit: 'HOURS') diff --git a/.ci/dev/nightly-regression/Jenkinsfile b/.ci/dev/nightly-regression/Jenkinsfile index 1b8739fe7b..2f4389a801 100644 --- a/.ci/dev/nightly-regression/Jenkinsfile +++ b/.ci/dev/nightly-regression/Jenkinsfile @@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() overrideIndexTriggers(false) diff --git a/.ci/dev/on-demand-tests/Jenkinsfile b/.ci/dev/on-demand-tests/Jenkinsfile index f59d3d67d0..25127ef133 100644 --- a/.ci/dev/on-demand-tests/Jenkinsfile +++ b/.ci/dev/on-demand-tests/Jenkinsfile @@ -3,4 +3,4 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) -onDemandTestPipeline('local-k8s', '.ci/dev/on-demand-tests/commentMappings.yml') +onDemandTestPipeline('k8s', '.ci/dev/on-demand-tests/commentMappings.yml') diff --git a/.ci/dev/regression/Jenkinsfile b/.ci/dev/regression/Jenkinsfile index 0f785396bb..5ca24015bc 100644 --- a/.ci/dev/regression/Jenkinsfile +++ b/.ci/dev/regression/Jenkinsfile @@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) diff --git a/.ci/dev/smoke/Jenkinsfile b/.ci/dev/smoke/Jenkinsfile index 05aec41e59..3ddc3cdce8 100644 --- a/.ci/dev/smoke/Jenkinsfile +++ b/.ci/dev/smoke/Jenkinsfile @@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() overrideIndexTriggers(false) diff --git a/.ci/dev/unit/Jenkinsfile b/.ci/dev/unit/Jenkinsfile index 0b7facd77c..b2d2d54393 100644 --- a/.ci/dev/unit/Jenkinsfile +++ b/.ci/dev/unit/Jenkinsfile @@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() timeout(time: 3, unit: 'HOURS') diff --git a/Jenkinsfile b/Jenkinsfile index fa039b4fdc..6d8e35669f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() timeout(time: 3, unit: 'HOURS') From f4c1119727b7cee6f99a73eb7d6b36373d5034b8 Mon Sep 17 00:00:00 2001 From: Razvan Codreanu <52859362+Schife@users.noreply.github.com> Date: Fri, 17 Apr 2020 15:53:10 +0100 Subject: [PATCH 05/10] INFRA-284 switching from local k8s label (#6156) --- .ci/dev/nightly-regression/Jenkinsfile | 2 +- .ci/dev/on-demand-tests/Jenkinsfile | 2 +- .ci/dev/regression/Jenkinsfile | 2 +- .ci/dev/smoke/Jenkinsfile | 2 +- .ci/dev/unit/Jenkinsfile | 2 +- Jenkinsfile | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.ci/dev/nightly-regression/Jenkinsfile b/.ci/dev/nightly-regression/Jenkinsfile index de26a41c90..7dd4301440 100644 --- a/.ci/dev/nightly-regression/Jenkinsfile +++ b/.ci/dev/nightly-regression/Jenkinsfile @@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() overrideIndexTriggers(false) diff --git a/.ci/dev/on-demand-tests/Jenkinsfile b/.ci/dev/on-demand-tests/Jenkinsfile index f59d3d67d0..25127ef133 100644 --- a/.ci/dev/on-demand-tests/Jenkinsfile +++ b/.ci/dev/on-demand-tests/Jenkinsfile @@ -3,4 +3,4 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) -onDemandTestPipeline('local-k8s', '.ci/dev/on-demand-tests/commentMappings.yml') +onDemandTestPipeline('k8s', '.ci/dev/on-demand-tests/commentMappings.yml') diff --git a/.ci/dev/regression/Jenkinsfile b/.ci/dev/regression/Jenkinsfile index ed550bd401..41cc2ad218 100644 --- a/.ci/dev/regression/Jenkinsfile +++ b/.ci/dev/regression/Jenkinsfile @@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) diff --git a/.ci/dev/smoke/Jenkinsfile b/.ci/dev/smoke/Jenkinsfile index 05aec41e59..3ddc3cdce8 100644 --- a/.ci/dev/smoke/Jenkinsfile +++ b/.ci/dev/smoke/Jenkinsfile @@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() overrideIndexTriggers(false) diff --git a/.ci/dev/unit/Jenkinsfile b/.ci/dev/unit/Jenkinsfile index 98f43b4428..65a6cc08ae 100644 --- a/.ci/dev/unit/Jenkinsfile +++ b/.ci/dev/unit/Jenkinsfile @@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() timeout(time: 3, unit: 'HOURS') diff --git a/Jenkinsfile b/Jenkinsfile index b81f50ed61..5f02f89de0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) pipeline { - agent { label 'local-k8s' } + agent { label 'k8s' } options { timestamps() timeout(time: 3, unit: 'HOURS') From 7e13491a25939b4db7831cd121afa60e35d9fc01 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Mon, 20 Apr 2020 17:49:12 +0100 Subject: [PATCH 06/10] CORDA-3716: Fix Enum serializers to handle enums that override toString() * CORDA-3716: Fix SandboxEnumSerializer to handle enums that override toString(). * Remove more uses of Enum.toString() from the Corda serializer. * Add test coverage for this case to standard enum serializer. * Increase maxWaitTimeout in IRSDemoTest to 150 seconds. --- .../kotlin/net/corda/irs/IRSDemoTest.kt | 2 +- .../djvm/deserializers/GetEnumNames.kt | 9 +++ .../corda/serialization/djvm/Serialization.kt | 5 +- .../djvm/serializers/SandboxEnumSerializer.kt | 13 +++- .../djvm/DeserializeCustomisedEnumTest.kt | 60 +++++++++++++++++++ .../serialization/djvm/LocalTypeModelTest.kt | 25 +++++++- .../internal/amqp/EnumSerializer.kt | 5 +- .../WhitelistBasedTypeModelConfiguration.kt | 5 +- .../model/LocalTypeInformationBuilder.kt | 6 +- .../internal/model/LocalTypeModel.kt | 2 +- .../serialization/internal/amqp/EnumTests.kt | 31 ++++++++++ .../internal/model/LocalTypeModelTests.kt | 22 +++++++ 12 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/GetEnumNames.kt create mode 100644 serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCustomisedEnumTest.kt diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index ec95489f1d..fd1a4917e1 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -48,7 +48,7 @@ class IRSDemoTest { private val rpcUsers = listOf(User("user", "password", setOf("ALL"))) private val currentDate: LocalDate = LocalDate.now() private val futureDate: LocalDate = currentDate.plusMonths(6) - private val maxWaitTime: Duration = 60.seconds + private val maxWaitTime: Duration = 150.seconds @Test(timeout=300_000) fun `runs IRS demo`() { diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/GetEnumNames.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/GetEnumNames.kt new file mode 100644 index 0000000000..5e60f530b4 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/GetEnumNames.kt @@ -0,0 +1,9 @@ +package net.corda.serialization.djvm.deserializers + +import java.util.function.Function + +class GetEnumNames : Function>, Array> { + override fun apply(enumValues: Array>): Array { + return enumValues.map(Enum<*>::name).toTypedArray() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt index 0c431e669b..fdf18afe99 100644 --- a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt @@ -12,6 +12,7 @@ import net.corda.djvm.rewiring.createSandboxPredicate import net.corda.djvm.rewiring.SandboxClassLoader import net.corda.serialization.djvm.deserializers.CheckEnum import net.corda.serialization.djvm.deserializers.DescribeEnum +import net.corda.serialization.djvm.deserializers.GetEnumNames import net.corda.serialization.djvm.serializers.PrimitiveSerializer import net.corda.serialization.internal.GlobalTransientClassWhiteList import net.corda.serialization.internal.SerializationContextImpl @@ -60,7 +61,9 @@ fun createSandboxSerializationEnv( @Suppress("unchecked_cast") val isEnumPredicate = predicateFactory.apply(CheckEnum::class.java) as Predicate> @Suppress("unchecked_cast") - val enumConstants = taskFactory.apply(DescribeEnum::class.java) as Function, Array> + val enumConstants = taskFactory.apply(DescribeEnum::class.java) + .andThen(taskFactory.apply(GetEnumNames::class.java)) + .andThen { (it as Array).map(Any::toString) } as Function, List> val sandboxLocalTypes = BaseLocalTypes( collectionClass = classLoader.toSandboxClass(Collection::class.java), diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt index 73b4421e19..a052e799da 100644 --- a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt @@ -4,6 +4,7 @@ import net.corda.core.serialization.SerializationContext import net.corda.djvm.rewiring.SandboxClassLoader import net.corda.serialization.djvm.deserializers.CheckEnum import net.corda.serialization.djvm.deserializers.DescribeEnum +import net.corda.serialization.djvm.deserializers.GetEnumNames import net.corda.serialization.djvm.toSandboxAnyClass import net.corda.serialization.internal.amqp.AMQPNotSerializableException import net.corda.serialization.internal.amqp.AMQPSerializer @@ -32,6 +33,10 @@ class SandboxEnumSerializer( private val describeEnum: Function, Array> = taskFactory.apply(DescribeEnum::class.java) as Function, Array> @Suppress("unchecked_cast") + private val getEnumNames: Function, List> + = (taskFactory.apply(GetEnumNames::class.java) as Function, Array>) + .andThen { it.map(Any::toString) } + @Suppress("unchecked_cast") private val isEnum: Predicate> = predicateFactory.apply(CheckEnum::class.java) as Predicate> @@ -46,7 +51,8 @@ class SandboxEnumSerializer( return null } val members = describeEnum.apply(declaredType) - return ConcreteEnumSerializer(declaredType, members, localFactory) + val memberNames = getEnumNames.apply(members) + return ConcreteEnumSerializer(declaredType, members, memberNames, localFactory) } override fun readObject( @@ -65,6 +71,7 @@ class SandboxEnumSerializer( private class ConcreteEnumSerializer( declaredType: Class<*>, private val members: Array, + private val memberNames: List, factory: LocalSerializerFactory ) : AMQPSerializer { override val type: Class<*> = declaredType @@ -78,7 +85,7 @@ private class ConcreteEnumSerializer( LocalTypeInformation.AnEnum( declaredType, TypeIdentifier.forGenericType(declaredType), - members.map(Any::toString), + memberNames, emptyList(), EnumTransforms.empty ) @@ -92,7 +99,7 @@ private class ConcreteEnumSerializer( val enumOrd = obj[1] as Int val fromOrd = members[enumOrd] - if (enumName != fromOrd.toString()) { + if (enumName != memberNames[enumOrd]) { throw AMQPNotSerializableException( type, "Deserializing obj as enum $type with value $enumName.$enumOrd but ordinality has changed" diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCustomisedEnumTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCustomisedEnumTest.kt new file mode 100644 index 0000000000..657ab25f34 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCustomisedEnumTest.kt @@ -0,0 +1,60 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeCustomisedEnumTest : TestBase(KOTLIN) { + @ParameterizedTest + @EnumSource(UserRole::class) + fun `test deserialize enum with custom toString`(role: UserRole) { + val userEnumData = UserEnumData(role) + val data = userEnumData.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxData = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showUserEnumData = taskFactory.compose(classLoader.createSandboxFunction()).apply(ShowUserEnumData::class.java) + val result = showUserEnumData.apply(sandboxData) ?: fail("Result cannot be null") + + assertEquals(ShowUserEnumData().apply(userEnumData), result.toString()) + assertEquals("UserRole: name='${role.roleName}', ordinal='${role.ordinal}'", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowUserEnumData : Function { + override fun apply(input: UserEnumData): String { + return with(input) { + "UserRole: name='${role.roleName}', ordinal='${role.ordinal}'" + } + } + } +} + +interface Role { + val roleName: String +} + +@Suppress("unused") +@CordaSerializable +enum class UserRole(override val roleName: String) : Role { + CONTROLLER(roleName = "Controller"), + WORKER(roleName = "Worker"); + + override fun toString() = roleName +} + +@CordaSerializable +data class UserEnumData(val role: UserRole) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalTypeModelTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalTypeModelTest.kt index 7ca5a3a61b..90b5d0813f 100644 --- a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalTypeModelTest.kt +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalTypeModelTest.kt @@ -38,13 +38,14 @@ class LocalTypeModelTest : TestBase(KOTLIN) { return classLoader.toSandboxClass(T::class.java) } - private inline fun assertLocalType(type: Class<*>) { - assertLocalType(LOCAL::class.java, type) + private inline fun assertLocalType(type: Class<*>): LOCAL { + return assertLocalType(LOCAL::class.java, type) as LOCAL } - private fun assertLocalType(localType: Class, type: Class<*>) { + private fun assertLocalType(localType: Class, type: Class<*>): LocalTypeInformation { val typeData = serializerFactory.getTypeInformation(type) assertThat(typeData).isInstanceOf(localType) + return typeData } @Test @@ -174,6 +175,14 @@ class LocalTypeModelTest : TestBase(KOTLIN) { assertLocalType(sandbox(classLoader)) } + @Test + fun testCustomEnum() = sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + val anEnum = assertLocalType(sandbox(classLoader)) + assertThat(anEnum.members) + .containsExactlyElementsOf(CustomEnum::class.java.enumConstants.map(CustomEnum::name)) + } + @Test fun testEnumSet() = sandbox { _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) @@ -188,4 +197,14 @@ class LocalTypeModelTest : TestBase(KOTLIN) { _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) assertLocalType(sandbox>(classLoader)) } + + @Suppress("unused") + enum class CustomEnum { + ONE, + TWO; + + override fun toString(): String { + return "[${name.toLowerCase()}]" + } + } } \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EnumSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EnumSerializer.kt index da8b922649..f4e9647486 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EnumSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EnumSerializer.kt @@ -14,11 +14,12 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Local override val typeDescriptor = factory.createDescriptor(type) init { + @Suppress("unchecked_cast") typeNotation = RestrictedType( AMQPTypeIdentifiers.nameForType(declaredType), null, emptyList(), "list", Descriptor(typeDescriptor), - declaredClass.enumConstants.zip(IntRange(0, declaredClass.enumConstants.size)).map { - Choice(it.first.toString(), it.second.toString()) + (declaredClass as Class>).enumConstants.zip(IntRange(0, declaredClass.enumConstants.size)).map { + Choice(it.first.name, it.second.toString()) }) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt index 3e9aea7aa0..fe9dcbb357 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/WhitelistBasedTypeModelConfiguration.kt @@ -53,6 +53,7 @@ private val opaqueTypes = setOf( Symbol::class.java ) +@Suppress("unchecked_cast") private val DEFAULT_BASE_TYPES = BaseLocalTypes( collectionClass = Collection::class.java, enumSetClass = EnumSet::class.java, @@ -60,5 +61,7 @@ private val DEFAULT_BASE_TYPES = BaseLocalTypes( mapClass = Map::class.java, stringClass = String::class.java, isEnum = Predicate { clazz -> clazz.isEnum }, - enumConstants = Function { clazz -> clazz.enumConstants } + enumConstants = Function { clazz -> + (clazz as Class>).enumConstants.map(Enum<*>::name) + } ) \ No newline at end of file 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 700cbf0ea8..add971b99a 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 @@ -119,7 +119,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, AnEnum( type, typeIdentifier, - enumConstants.map(Any::toString), + enumConstants, buildInterfaceInformation(type), getEnumTransforms(type, enumConstants) ) @@ -142,9 +142,9 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, } } - private fun getEnumTransforms(type: Class<*>, enumConstants: Array): EnumTransforms { + private fun getEnumTransforms(type: Class<*>, enumConstants: List): EnumTransforms { try { - val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant.toString() to index }.toMap() + val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant to index }.toMap() return EnumTransforms.build(TransformsAnnotationProcessor.getTransformsSchema(type), constants) } catch (e: InvalidEnumTransformsException) { throw NotSerializableDetailedException(type.name, e.message!!) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt index 19ac1e018b..6186a09dbf 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeModel.kt @@ -136,5 +136,5 @@ class BaseLocalTypes( val mapClass: Class<*>, val stringClass: Class<*>, val isEnum: Predicate>, - val enumConstants: Function, Array> + val enumConstants: Function, List> ) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumTests.kt index c2cda2c1cf..b406283d9f 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumTests.kt @@ -3,6 +3,8 @@ package net.corda.serialization.internal.amqp import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.serialization.internal.EmptyWhitelist import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput import net.corda.serialization.internal.amqp.testutils.deserialize import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope @@ -11,6 +13,7 @@ import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolu import net.corda.serialization.internal.amqp.testutils.testName import net.corda.serialization.internal.carpenter.ClassCarpenterImpl import org.assertj.core.api.Assertions +import org.junit.Assert.assertNotSame import org.junit.Test import java.io.NotSerializableException import java.time.DayOfWeek @@ -279,4 +282,32 @@ class EnumTests { DeserializationInput(factory2).deserialize(bytes) }.isInstanceOf(NotSerializableException::class.java) } + + @Test(timeout = 300_000) + fun deserializeCustomisedEnum() { + val input = CustomEnumWrapper(CustomEnum.ONE) + val factory1 = SerializerFactoryBuilder.build(EmptyWhitelist, ClassLoader.getSystemClassLoader()) + val serialized = TestSerializationOutput(VERBOSE, factory1).serialize(input) + + val factory2 = SerializerFactoryBuilder.build(EmptyWhitelist, ClassLoader.getSystemClassLoader()) + val output = DeserializationInput(factory2).deserialize(serialized) + + assertEquals(input, output) + assertNotSame("Deserialized object should be brand new.", input, output) + } + + @Suppress("unused") + @CordaSerializable + enum class CustomEnum { + ONE, + TWO, + THREE; + + override fun toString(): String { + return "[${name.toLowerCase()}]" + } + } + + @CordaSerializable + data class CustomEnumWrapper(val data: CustomEnum) } \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt index a1ecbe6799..8d74f3eef8 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/model/LocalTypeModelTests.kt @@ -4,9 +4,11 @@ import com.google.common.reflect.TypeToken import net.corda.core.serialization.SerializableCalculatedProperty import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.amqp.* +import org.assertj.core.api.Assertions.assertThat import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.jupiter.api.fail import java.lang.reflect.Type import java.util.* @@ -206,6 +208,26 @@ class LocalTypeModelTests { } } + @Suppress("unused") + enum class CustomEnum { + ONE, + TWO; + + override fun toString(): String { + return "[${name.toLowerCase()}]" + } + } + + @Test(timeout = 300_000) + fun `test type information for customised enum`() { + modelWithoutOpacity.inspect(typeOf()).let { typeInformation -> + val anEnum = typeInformation as? LocalTypeInformation.AnEnum ?: fail("Not AnEnum!") + assertThat(anEnum.members).containsExactlyElementsOf( + CustomEnum::class.java.enumConstants.map(CustomEnum::name) + ) + } + } + private inline fun assertInformation(expected: String) { assertEquals(expected.trimIndent(), model.inspect(typeOf()).prettyPrint()) } From 02d21c7bac51238f52be694716439561ea51c97e Mon Sep 17 00:00:00 2001 From: nikinagy <61757742+nikinagy@users.noreply.github.com> Date: Wed, 22 Apr 2020 13:34:17 +0100 Subject: [PATCH 07/10] making sure hibernate uses UTC time zone (#6168) --- .../corda/nodeapi/internal/persistence/HibernateConfiguration.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt index 0fca5b23bd..ebd023f155 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt @@ -91,6 +91,7 @@ class HibernateConfiguration( .setProperty("hibernate.hbm2ddl.auto", hbm2dll) .setProperty("javax.persistence.validation.mode", "none") .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) + .setProperty("hibernate.jdbc.time_zone", "UTC") schemas.forEach { schema -> // TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session From 69a4f80cd22f0885f82ecbb61efd22b3e71c7358 Mon Sep 17 00:00:00 2001 From: Joseph Zuniga-Daly <59851625+josephzunigadaly@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:11:23 +0100 Subject: [PATCH 08/10] ENT-5141: Fix ConcurrentModificationException in FetchDataFlow (#6176) * ENT-5141: Fix ConcurrentModificationException in FetchDataFlow * Make detekt happy * Make CheckpointSerializationEnvironmentRule inheritable --- .../kryo/DefaultKryoCustomizer.kt | 4 + .../serialization/kryo/IteratorSerializer.kt | 52 ++++++++ ...yListItrConcurrentModificationException.kt | 122 ++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt create mode 100644 node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/DefaultKryoCustomizer.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/DefaultKryoCustomizer.kt index b8130dce5f..504b17aff6 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/DefaultKryoCustomizer.kt @@ -129,6 +129,10 @@ object DefaultKryoCustomizer { register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer) register(ContractUpgradeFilteredTransaction::class.java, ContractUpgradeFilteredTransactionSerializer) + addDefaultSerializer(Iterator::class.java) {kryo, type -> + IteratorSerializer(type, CompatibleFieldSerializer>(kryo, type).apply { setIgnoreSyntheticFields(false) }) + } + for (whitelistProvider in serializationWhitelists) { val types = whitelistProvider.whitelist require(types.toSet().size == types.size) { diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt new file mode 100644 index 0000000000..382ae840c5 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt @@ -0,0 +1,52 @@ +package net.corda.node.serialization.kryo + +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.Serializer +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output +import java.lang.reflect.Field + +class IteratorSerializer(type: Class<*>, private val serializer: Serializer>) : Serializer>(false, false) { + + private val iterableReferenceField = findField(type, "this\$0")?.apply { isAccessible = true } + private val expectedModCountField = findField(type, "expectedModCount")?.apply { isAccessible = true } + private val iterableReferenceFieldType = iterableReferenceField?.type + private val modCountField = when (iterableReferenceFieldType) { + null -> null + else -> findField(iterableReferenceFieldType, "modCount")?.apply { isAccessible = true } + } + + override fun write(kryo: Kryo, output: Output, obj: Iterator<*>) { + serializer.write(kryo, output, obj) + } + + override fun read(kryo: Kryo, input: Input, type: Class>): Iterator<*> { + val iterator = serializer.read(kryo, input, type) + return fixIterator(iterator) + } + + private fun fixIterator(iterator: Iterator<*>) : Iterator<*> { + + // Set expectedModCount of iterator + val iterableInstance = iterableReferenceField?.get(iterator) ?: return iterator + val modCountValue = modCountField?.getInt(iterableInstance) ?: return iterator + expectedModCountField?.setInt(iterator, modCountValue) + + return iterator + } + + /** + * Find field in clazz or any superclass + */ + private fun findField(clazz: Class<*>, fieldName: String): Field? { + return clazz.declaredFields.firstOrNull { x -> x.name == fieldName } ?: when { + clazz.superclass != null -> { + // Look in superclasses + findField(clazz.superclass, fieldName) + } + else -> null // Not found + } + } +} + + diff --git a/node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt b/node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt new file mode 100644 index 0000000000..44a48c793d --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt @@ -0,0 +1,122 @@ +package net.corda.node.serialization.kryo + +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.serialization.EncodingWhitelist +import net.corda.core.serialization.internal.CheckpointSerializationContext +import net.corda.core.serialization.internal.checkpointDeserialize +import net.corda.core.serialization.internal.checkpointSerialize +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.CheckpointSerializationContextImpl +import net.corda.serialization.internal.CordaSerializationEncoding +import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule +import net.corda.testing.internal.rigorousMock +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.HashSet +import kotlin.collections.LinkedHashMap +import kotlin.collections.LinkedHashSet + +@RunWith(Parameterized::class) +class ArrayListItrConcurrentModificationException(private val compression: CordaSerializationEncoding?) { + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun compression() = arrayOf(null) + CordaSerializationEncoding.values() + } + + @get:Rule + val serializationRule = CheckpointSerializationEnvironmentRule(inheritable = true) + private lateinit var context: CheckpointSerializationContext + + @Before + fun setup() { + context = CheckpointSerializationContextImpl( + deserializationClassLoader = javaClass.classLoader, + whitelist = AllWhitelist, + properties = emptyMap(), + objectReferencesEnabled = true, + encoding = compression, + encodingWhitelist = rigorousMock().also { + if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression) + }) + } + + @Test(timeout=300_000) + fun `ArrayList iterator can checkpoint without error`() { + runTestWithCollection(ArrayList()) + } + + @Test(timeout=300_000) + fun `HashSet iterator can checkpoint without error`() { + runTestWithCollection(HashSet()) + } + + @Test(timeout=300_000) + fun `LinkedHashSet iterator can checkpoint without error`() { + runTestWithCollection(LinkedHashSet()) + } + + @Test(timeout=300_000) + fun `HashMap iterator can checkpoint without error`() { + runTestWithCollection(HashMap()) + } + + @Test(timeout=300_000) + fun `LinkedHashMap iterator can checkpoint without error`() { + runTestWithCollection(LinkedHashMap()) + } + + @Test(timeout=300_000) + fun `LinkedList iterator can checkpoint without error`() { + runTestWithCollection(LinkedList()) + } + + private data class TestCheckpoint(val list: C, val iterator: I) + + private fun runTestWithCollection(collection: MutableCollection) { + + for (i in 1..100) { + collection.add(i) + } + + val iterator = collection.iterator() + iterator.next() + + val checkpoint = TestCheckpoint(collection, iterator) + + val serializedBytes = checkpoint.checkpointSerialize(context) + val deserializedCheckpoint = serializedBytes.checkpointDeserialize(context) + + assertThat(deserializedCheckpoint.list).isEqualTo(collection) + assertThat(deserializedCheckpoint.iterator.next()).isEqualTo(2) + assertThat(deserializedCheckpoint.iterator.hasNext()).isTrue() + } + + private fun runTestWithCollection(collection: MutableMap) { + + for (i in 1..100) { + collection[i] = i + } + + val iterator = collection.iterator() + iterator.next() + + val checkpoint = TestCheckpoint(collection, iterator) + + val serializedBytes = checkpoint.checkpointSerialize(context) + val deserializedCheckpoint = serializedBytes.checkpointDeserialize(context) + + assertThat(deserializedCheckpoint.list).isEqualTo(collection) + assertThat(deserializedCheckpoint.iterator.next().key).isEqualTo(2) + assertThat(deserializedCheckpoint.iterator.hasNext()).isTrue() + } +} From ee7700e28fcb18023ef53a9142ae401a42172902 Mon Sep 17 00:00:00 2001 From: nikinagy Date: Fri, 24 Apr 2020 17:00:10 +0100 Subject: [PATCH 09/10] Add Hibernate UTC Fix --- .../internal/persistence/factory/BaseSessionFactoryFactory.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt index a09022f52b..345b9c6452 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt @@ -43,6 +43,7 @@ abstract class BaseSessionFactoryFactory : CordaSessionFactoryFactory { .setProperty("javax.persistence.validation.mode", "none") .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) .setProperty("hibernate.hbm2ddl.auto", hbm2dll) + .setProperty("hibernate.jdbc.time_zone", "UTC") } override fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, attributeConverters: Collection>): Metadata { From e6184168612d22fa90fb90ee935c62b17cd16046 Mon Sep 17 00:00:00 2001 From: Joseph Zuniga-Daly Date: Fri, 24 Apr 2020 17:11:15 +0100 Subject: [PATCH 10/10] Move files to their new home --- .../internal}/serialization/kryo/IteratorSerializer.kt | 2 +- .../kryo/ArrayListItrConcurrentModificationException.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename {node/src/main/kotlin/net/corda/node => node-api/src/main/kotlin/net/corda/nodeapi/internal}/serialization/kryo/IteratorSerializer.kt (97%) rename {node/src/test/kotlin/net/corda/node => node-api/src/test/kotlin/net/corda/nodeapi/internal}/serialization/kryo/ArrayListItrConcurrentModificationException.kt (97%) diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/IteratorSerializer.kt similarity index 97% rename from node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/IteratorSerializer.kt index 382ae840c5..601f384593 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/IteratorSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/IteratorSerializer.kt @@ -1,4 +1,4 @@ -package net.corda.node.serialization.kryo +package net.corda.nodeapi.internal.serialization.kryo import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Serializer diff --git a/node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/ArrayListItrConcurrentModificationException.kt similarity index 97% rename from node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/ArrayListItrConcurrentModificationException.kt index 44a48c793d..588ad953d9 100644 --- a/node/src/test/kotlin/net/corda/node/serialization/kryo/ArrayListItrConcurrentModificationException.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/ArrayListItrConcurrentModificationException.kt @@ -1,4 +1,4 @@ -package net.corda.node.serialization.kryo +package net.corda.nodeapi.internal.serialization.kryo import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever @@ -6,11 +6,11 @@ import net.corda.core.serialization.EncodingWhitelist import net.corda.core.serialization.internal.CheckpointSerializationContext import net.corda.core.serialization.internal.checkpointDeserialize import net.corda.core.serialization.internal.checkpointSerialize +import net.corda.coretesting.internal.rigorousMock import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.CheckpointSerializationContextImpl import net.corda.serialization.internal.CordaSerializationEncoding import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule -import net.corda.testing.internal.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule