From 78e675424ebed5bb36e5d076252a05a424e5a170 Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Tue, 30 Mar 2021 12:01:49 -0700 Subject: [PATCH] feat(corda-connector): dsl to support collections, enums #622 Primary change ============ You can now express via the JSON DSL otherwise non-constructable types such as ByteArray, DoubleArrays, enums, lists, maps, sets, etc. The collections are being powered by a pre-populated map of so called "exotic" types which are named as such because they do not follow the Java OOP ideals of having a constructor responsible for creating the class instances. Added test coverage for all the above as well. Miscellaneous changes: =================== Deleted the Kotlin test that was dependent on a corda network being pulled up and cordapps deployed because we are covering all of this via the existing tests written in Typescript anyway. With the above done, now the dockerfile can be switched back to execute the tests of the corda plugin server (which are only unit tests at this point that is more than enough since we have coverage elsewhere). Tagged the container image built from this revision as: hyperledger/cactus-connector-corda-server:2021-03-25-feat-622 Fixes #622 Signed-off-by: Peter Somogyvari --- .../src/main-server/Dockerfile | 5 +- .../kotlin-spring/.openapi-generator-ignore | 1 + .../server/impl/ConstructorLookupException.kt | 10 + .../server/impl/JsonJvmObjectDeserializer.kt | 112 +++-- .../api/ApiPluginLedgerConnectorCordaTest.kt | 271 ------------ .../impl/JsonJvmObjectDeserializerTest.kt | 414 ++++++++++++++++++ .../deploy-cordapp-jars-to-nodes.test.ts | 2 +- .../jvm-kotlin-spring-server.test.ts | 2 +- 8 files changed, 513 insertions(+), 304 deletions(-) create mode 100644 packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/ConstructorLookupException.kt delete mode 100644 packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCordaTest.kt create mode 100644 packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializerTest.kt diff --git a/packages/cactus-plugin-ledger-connector-corda/src/main-server/Dockerfile b/packages/cactus-plugin-ledger-connector-corda/src/main-server/Dockerfile index fe77d434ce..eee7187fd6 100644 --- a/packages/cactus-plugin-ledger-connector-corda/src/main-server/Dockerfile +++ b/packages/cactus-plugin-ledger-connector-corda/src/main-server/Dockerfile @@ -4,9 +4,8 @@ WORKDIR / COPY ./kotlin/gen/kotlin-spring /kotlin-spring/ WORKDIR /kotlin-spring/ -# We skip the tests because they require a working -# Corda network which we do not want to set up. -RUN gradle build -x test + +RUN gradle build WORKDIR / FROM openjdk:8u275-jre-slim-buster diff --git a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/.openapi-generator-ignore b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/.openapi-generator-ignore index 260a41ffcf..85d3b7cde3 100644 --- a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/.openapi-generator-ignore +++ b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/.openapi-generator-ignore @@ -23,6 +23,7 @@ #!docs/README.md build.gradle.kts +src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCordaTest.kt src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/ApiPluginLedgerConnectorCordaServiceImpl.kt src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/Application.kt .gitignore \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/ConstructorLookupException.kt b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/ConstructorLookupException.kt new file mode 100644 index 0000000000..ceec019fd4 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/ConstructorLookupException.kt @@ -0,0 +1,10 @@ +package org.hyperledger.cactus.plugin.ledger.connector.corda.server.impl + +import java.lang.RuntimeException + + +open class InstantiationException(override val message: String, override val cause: Throwable? = null) + : RuntimeException(message, cause) + +open class ConstructorLookupException(override val message: String, override val cause: Throwable? = null) + : InstantiationException(message, cause) diff --git a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializer.kt b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializer.kt index aa35ae5c30..e4a20462aa 100644 --- a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializer.kt +++ b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializer.kt @@ -10,6 +10,7 @@ import java.lang.RuntimeException import java.lang.reflect.Constructor import java.lang.reflect.Method import java.util.* +import kotlin.collections.ArrayList // FIXME: Make it so that this has a memory, remembering the .jar files that were added before (file-system?) or // maybe use the keychain to save it there and then it can pre-populate at boot? @@ -73,20 +74,92 @@ class JsonJvmObjectDeserializer( val constructorArgs: Array = jvmObject.jvmCtorArgs.map { x -> instantiate(x) }.toTypedArray() when { - List::class.java.isAssignableFrom(clazz) -> { - return listOf(*constructorArgs) + DoubleArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Double } + .toDoubleArray() + } + FloatArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Float } + .toFloatArray() + } + LongArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Long } + .toLongArray() + } + ShortArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Short } + .toShortArray() + } + CharArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Char } + .toCharArray() + } + BooleanArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Boolean } + .toBooleanArray() + } + IntArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Int } + .toIntArray() + } + ByteArray::class.java.isAssignableFrom(clazz) -> { + return constructorArgs + .map { ca -> ca as Byte } + .toByteArray() + } + ArrayList::class.java.isAssignableFrom(clazz) -> { + return arrayListOf(*constructorArgs) } Array::class.java.isAssignableFrom(clazz) -> { - // TODO verify that this actually works and also - // if we need it at all since we already have lists covered return arrayOf(*constructorArgs) } + List::class.java.isAssignableFrom(clazz) -> { + return listOf(*constructorArgs) + } + Set::class.java.isAssignableFrom(clazz) -> { + return setOf(*constructorArgs) + } + Map::class.java.isAssignableFrom(clazz) -> { + val constructorArgsCasted = constructorArgs + .map { ca -> ca as Pair<*, *> } + .toTypedArray() + + return mapOf(*constructorArgsCasted) + } jvmObject.jvmType.constructorName != null -> { val methodArgTypes: List> = jvmObject.jvmCtorArgs.map { x -> getOrInferType(x.jvmType.fqClassName) } + + var invocationTarget: Any? = null + if (jvmObject.jvmType.invocationTarget != null) { + try { + logger.debug("Instantiating InvocationTarget: ${jvmObject.jvmType.invocationTarget}") + invocationTarget = instantiate(jvmObject.jvmType.invocationTarget) + logger.debug("Instantiated OK InvocationTarget: ${jvmObject.jvmType.invocationTarget}") + logger.debug("InvocationTarget: $invocationTarget") + } catch (ex: Exception) { + val argTypes = jvmObject.jvmCtorArgs.joinToString(",") { x -> x.jvmType.fqClassName } + val className = jvmObject.jvmType.fqClassName + val constructorName = jvmObject.jvmType.constructorName + val message = "Failed to instantiate invocation target for " + + "JvmType:${className}${constructorName}(${argTypes}) with an " + + "InvocationTarget: ${jvmObject.jvmType.invocationTarget}" + throw InstantiationException(message, ex) + } + } + + val factoryClass: Class<*> = if (invocationTarget == null) clazz else invocationTarget::class.java + val factoryMethod: Method try { - factoryMethod = clazz.methods + factoryMethod = factoryClass.methods .filter { c -> c.name == jvmObject.jvmType.constructorName } .filter { c -> c.parameterCount == methodArgTypes.size } .single { c -> @@ -96,35 +169,18 @@ class JsonJvmObjectDeserializer( } } catch (ex: NoSuchElementException) { val argTypes = jvmObject.jvmCtorArgs.joinToString(",") { x -> x.jvmType.fqClassName } - val className = jvmObject.jvmType.fqClassName - val methodsAsStrings = clazz.constructors - .mapIndexed { i, c -> "$className->Method#${i + 1}(${c.parameterTypes.joinToString { p -> p.name }})" } - .joinToString(" ;; ") - val targetMethod = "Cannot find matching method for ${className}(${argTypes})" + val className = factoryClass.name + val methodsAsStrings = + factoryClass.methods.joinToString("\n") { c -> "$className#${c.name}(${c.parameterTypes.joinToString { p -> p.name }})" } + val targetMethod = "Cannot find matching method for ${className}#${jvmObject.jvmType.constructorName}(${argTypes})" val availableMethods = - "Searched among the ${clazz.constructors.size} available methods: $methodsAsStrings" - throw RuntimeException("$targetMethod --- $availableMethods") + "Searched among the ${clazz.methods.size} available methods: $methodsAsStrings" + throw ConstructorLookupException("$targetMethod --- $availableMethods") } logger.info("Constructor=${factoryMethod}") constructorArgs.forEachIndexed { index, it -> logger.info("Constructor ARGS: #${index} -> $it") } - var invocationTarget: Any? = null - if (jvmObject.jvmType.invocationTarget != null) { - try { - logger.debug("Instantiating InvocationTarget: ${jvmObject.jvmType.invocationTarget}") - invocationTarget = instantiate(jvmObject.jvmType.invocationTarget) - logger.debug("Instantiated OK InvocationTarget: ${jvmObject.jvmType.invocationTarget}") - } catch (ex: Exception) { - val argTypes = jvmObject.jvmCtorArgs.joinToString(",") { x -> x.jvmType.fqClassName } - val className = jvmObject.jvmType.fqClassName - val constructorName = jvmObject.jvmType.constructorName - val message = "Failed to instantiate invocation target for " + - "JvmType:${className}${constructorName}(${argTypes}) with an " + - "InvocationTarget: ${jvmObject.jvmType.invocationTarget}" - throw RuntimeException(message, ex) - } - } val instance = factoryMethod.invoke(invocationTarget, *constructorArgs) logger.info("Instantiated REFERENCE OK {}", instance) return instance diff --git a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCordaTest.kt b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCordaTest.kt deleted file mode 100644 index b310afc68a..0000000000 --- a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCordaTest.kt +++ /dev/null @@ -1,271 +0,0 @@ -package org.hyperledger.cactus.plugin.ledger.connector.corda.server.api - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.* -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest - -import kotlin.test.assertNotNull - -@SpringBootTest -class ApiPluginLedgerConnectorCordaTest { - - @Autowired - private lateinit var service: ApiPluginLedgerConnectorCordaService; - - /** - * Deploys a set of jar files (Cordapps, e.g. the contracts in Corda speak). - * - * - * - * @throws ApiException - * if the Api call fails - */ -// @Test -// fun deployContractJarsV1Test() { -// val api: ApiPluginLedgerConnectorCordaController = ApiPluginLedgerConnectorCordaController(service) -// val deployContractJarsV1Request = DeployContractJarsV1Request( -// jarFiles = listOf(JarFile(filename = "my-cool-contract.jar", contentBase64 = "x=="))) -// val response: ResponseEntity = api.deployContractJarsV1(deployContractJarsV1Request) -// -// println("TEST OK") -// assert(true) -// // TODO: test validations -// } - - /** - * Invokes a contract on a Corda ledger (e.g. a flow) - * - * - * - * @throws ApiException - * if the Api call fails - */ - @Test - fun invokeContractV1Test() { - val api: ApiPluginLedgerConnectorCordaController = ApiPluginLedgerConnectorCordaController(service) - val reqBodyJson = """ - { - "flowFullClassName" : "net.corda.samples.obligation.flows.IOUIssueFlow", - "flowInvocationType" : "TRACKED_FLOW_DYNAMIC", - "params" : [ { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.samples.obligation.states.IOUState" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.contracts.Amount" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "long" - }, - "primitiveValue" : 42, - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "java.util.Currency" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "USD", - "jvmCtorArgs" : null - } ] - } ] - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.identity.Party" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.identity.CordaX500Name" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "PartyA", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "London", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "GB", - "jvmCtorArgs" : null - } ] - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "org.hyperledger.cactus.plugin.ledger.connector.corda.server.impl.PublicKeyImpl" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "EdDSA", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "X.509", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "MCowBQYDK2VwAyEAac1p4wLsAh70VJOcudQppu7NnKxyoKxVN0DbfTxF+54=", - "jvmCtorArgs" : null - } ] - } ] - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.identity.Party" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.identity.CordaX500Name" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "PartyB", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "New York", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "US", - "jvmCtorArgs" : null - } ] - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "org.hyperledger.cactus.plugin.ledger.connector.corda.server.impl.PublicKeyImpl" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "EdDSA", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "X.509", - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "MCowBQYDK2VwAyEAoOv19eiCDJ7HzR9UrfwbFig7qcD1jkewKkkS4WF9kPA=", - "jvmCtorArgs" : null - } ] - } ] - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.contracts.Amount" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "long" - }, - "primitiveValue" : 1, - "jvmCtorArgs" : null - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "java.util.Currency" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "USD", - "jvmCtorArgs" : null - } ] - } ] - }, { - "jvmTypeKind" : "REFERENCE", - "jvmType" : { - "fqClassName" : "net.corda.core.contracts.UniqueIdentifier" - }, - "primitiveValue" : null, - "jvmCtorArgs" : [ { - "jvmTypeKind" : "PRIMITIVE", - "jvmType" : { - "fqClassName" : "java.lang.String" - }, - "primitiveValue" : "7fc2161e-f8d0-4c86-a596-08326bdafd56", - "jvmCtorArgs" : null - } ] - } ] - } ], - "timeoutMs" : 60000 - } - """.trimIndent() - - // Vanilla ObjectMapper is no good here because: https://stackoverflow.com/a/53191565/698470 - val objectMapper = jacksonObjectMapper() - - val jsonReader = objectMapper.readerFor(InvokeContractV1Request::class.java) - val req = jsonReader.readValue(reqBodyJson) - - val res = api.invokeContractV1(req) - assertNotNull(res, "invokeContractV1 response was null") - assertNotNull(res.statusCode, "invokeContractV1 response.statusCode was null") - } - -} diff --git a/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializerTest.kt b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializerTest.kt new file mode 100644 index 0000000000..0853fa7541 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/test/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializerTest.kt @@ -0,0 +1,414 @@ +package org.hyperledger.cactus.plugin.ledger.connector.corda.server.impl + +import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.* +import org.junit.jupiter.api.Test +import java.util.* +import kotlin.collections.ArrayList + +enum class Direction { + NORTH, SOUTH, WEST, EAST +} + +class JsonJvmObjectDeserializerTest { + + companion object { + val deserializer = JsonJvmObjectDeserializer() + } + + @Test + fun enumHappyPath() { + val actual = Direction.WEST + + class FlightPlan(val direction: Direction) + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = FlightPlan::class.java.name + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Direction::class.java.name), + primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is FlightPlan) + val flightPlan = deserializedObject as FlightPlan + val expected = flightPlan.direction + assert(actual == expected) + } + + @Test + fun floatArrayHappyPath() { + + val actual = 42.42F + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = FloatArray::class.java.name + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Double::class.java.name), + primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is FloatArray) + val array = deserializedObject as FloatArray + val expected = array[0] + assert(actual == expected) + } + + @Test + fun doubleArrayHappyPath() { + + val actual = 42.42 + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = DoubleArray::class.java.name + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Double::class.java.name), + primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is DoubleArray) + val array = deserializedObject as DoubleArray + val expected = array[0] + assert(actual == expected) + } + + @Test + fun intArrayHappyPath() { + + val actual = 42 + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = IntArray::class.java.name + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Int::class.java.name), + primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is IntArray) + val anIntArray = deserializedObject as IntArray + val expected = anIntArray[0] + assert(actual == expected) + } + + @Test + fun byteArrayHappyPath() { + + val actual: Byte = 42 + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = ByteArray::class.java.name + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Byte::class.java.name), + primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is ByteArray) + val byteArray = deserializedObject as ByteArray + val expected = byteArray[0] + assert(actual == expected) + } + + @Test + fun booleanArrayHappyPath() { + + val actual1 = true + val actual2 = false + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = BooleanArray::class.java.name + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Boolean::class.java.name), + primitiveValue = actual1 + ), + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(Boolean::class.java.name), + primitiveValue = actual2 + ) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is BooleanArray) + val array = deserializedObject as BooleanArray + val expected1 = array[0] + assert(actual1 == expected1) + + val expected2 = array[1] + assert(actual2 == expected2) + } + + @Test + fun invocationTargetHappyPath() { + + data class MyCustomClass(val id: String) + + class MyCustomFactory { + fun createMyCustomClass(id: String): MyCustomClass { + return MyCustomClass(id) + } + } + + val actual = UUID.randomUUID().toString() + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = MyCustomClass::class.java.name, + constructorName = "createMyCustomClass", + invocationTarget = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = MyCustomFactory::class.java.name + ), + jvmCtorArgs = emptyList() + ) + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(String::class.java.name), + primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is MyCustomClass) + val myCustomClass = deserializedObject as MyCustomClass + val expected = myCustomClass.id + assert(actual == expected) + } + + @Test + fun invocationTargetSadPath() { + data class MyCustomClass(val id: String) + + class MyCustomFactory { + fun createMyCustomClass(id: String): MyCustomClass { + return MyCustomClass(id) + } + } + + val actual = UUID.randomUUID().toString() + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = MyCustomClass::class.java.name, + constructorName = "createMyCustomClass", + invocationTarget = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = MyCustomFactory::class.java.name + ), + jvmCtorArgs = listOf( + // this will make the constructor of the factory class (the invocation target) + // crash hard and we'll be there to assert that the exception message contains + // useful information + JvmObject(JvmTypeKind.PRIMITIVE, JvmType(Long::class.java.name), 1L) + ) + ) + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(String::class.java.name), + primitiveValue = actual) + ) + ) + + try { + deserializer.instantiate(jvmObject) + } catch (ex: InstantiationException) { + assert(ex.message.contains("Failed to instantiate invocation target for")) + val factoryMethodName = jvmObject.jvmType.constructorName as String + val factoryClassName = jvmObject.jvmType.invocationTarget?.jvmType?.fqClassName + assert(ex.message.contains(factoryMethodName)) + assert(ex.message.contains(factoryClassName as String)) + } + } + + @Test + fun factoryMethodSadPath() { + data class MyCustomClass(val id: String) + + class MyCustomFactory { + fun createMyCustomClass(id: String): MyCustomClass { + return MyCustomClass(id) + } + } + + val actual = UUID.randomUUID().toString() + + val jvmObject = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = MyCustomClass::class.java.name, + constructorName = "some-non-existent-method-name", + invocationTarget = JvmObject( + jvmTypeKind = JvmTypeKind.REFERENCE, + jvmType = JvmType( + fqClassName = MyCustomFactory::class.java.name + ), + jvmCtorArgs = emptyList() + ) + ), + jvmCtorArgs = listOf( + JvmObject( + jvmTypeKind = JvmTypeKind.PRIMITIVE, + jvmType = JvmType(String::class.java.name), + primitiveValue = actual) + ) + ) + + try { + deserializer.instantiate(jvmObject) + } catch (ex: ConstructorLookupException) { + assert(ex.message.contains("Cannot find matching method for")) + val factoryMethodName = jvmObject.jvmType.constructorName as String + val factoryClassName = jvmObject.jvmType.invocationTarget?.jvmType?.fqClassName + assert(ex.message.contains(factoryMethodName)) + assert(ex.message.contains(factoryClassName as String)) + } + } + + @Test + fun listOf() { + val actual = UUID.randomUUID().toString() + val jvmObject = JvmObject( + JvmTypeKind.REFERENCE, + JvmType(List::class.java.name), + jvmCtorArgs = listOf( + JvmObject(JvmTypeKind.PRIMITIVE, JvmType(String::class.java.name), primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is List<*>) + val list = deserializedObject as List<*> + assert(list.size == 1) + val expected = list[0] + assert(actual == expected) + } + + @Test + fun arrayOf() { + val actual = UUID.randomUUID().toString() + val jvmObject = JvmObject( + JvmTypeKind.REFERENCE, + JvmType(Array::class.java.name), + jvmCtorArgs = listOf( + JvmObject(JvmTypeKind.PRIMITIVE, JvmType(String::class.java.name), primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is Array<*>) + val list = deserializedObject as Array<*> + assert(list.size == 1) + val expected = list[0] + assert(actual == expected) + } + + @Test + fun arrayListOf() { + val actual = UUID.randomUUID().toString() + val jvmObject = JvmObject( + JvmTypeKind.REFERENCE, + JvmType(ArrayList::class.java.name), + jvmCtorArgs = listOf( + JvmObject(JvmTypeKind.PRIMITIVE, JvmType(String::class.java.name), primitiveValue = actual) + ) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is ArrayList<*>) + val list = deserializedObject as ArrayList<*> + assert(list.size == 1) + val expected = list[0] + assert(actual == expected) + } + + @Test + fun setOf() { + val actual = UUID.randomUUID().toString() + val listJvmObject = JvmObject( + JvmTypeKind.REFERENCE, + JvmType(Set::class.java.name), + jvmCtorArgs = listOf( + JvmObject(JvmTypeKind.PRIMITIVE, JvmType(String::class.java.name), primitiveValue = actual) + ) + ) + + val jvmObject = deserializer.instantiate(listJvmObject) + assert(jvmObject is Set<*>) + val list = jvmObject as Set<*> + assert(list.size == 1) + val containsActual = list.contains(actual) + assert(containsActual) + } + + @Test + fun mapOf() { + val actualKey = UUID.randomUUID().toString() + val actualValue = UUID.randomUUID().toString() + val actualKeyJvmObject = JvmObject(JvmTypeKind.PRIMITIVE, JvmType(String::class.java.name), primitiveValue = actualKey) + val actualValueJvmObject = JvmObject(JvmTypeKind.PRIMITIVE, JvmType(String::class.java.name), primitiveValue = actualValue) + val actualPairJvmObject = JvmObject( + JvmTypeKind.REFERENCE, + jvmType = JvmType(Pair::class.java.name), + jvmCtorArgs = listOf(actualKeyJvmObject, actualValueJvmObject) + ) + val jvmObject = JvmObject( + JvmTypeKind.REFERENCE, + JvmType(Map::class.java.name), + jvmCtorArgs = listOf(actualPairJvmObject) + ) + + val deserializedObject = deserializer.instantiate(jvmObject) + assert(deserializedObject is Map<*, *>) + val map = deserializedObject as Map<*, *> + assert(map.keys.contains(actualKey)) + val expectedValue = map[actualKey] + assert(actualValue == expectedValue) + } + +} diff --git a/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/deploy-cordapp-jars-to-nodes.test.ts b/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/deploy-cordapp-jars-to-nodes.test.ts index 0ef7d62d32..84d8d98ca0 100644 --- a/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/deploy-cordapp-jars-to-nodes.test.ts +++ b/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/deploy-cordapp-jars-to-nodes.test.ts @@ -86,7 +86,7 @@ test("Tests are passing on the JVM side", async (t: Test) => { const connector = new CordaConnectorContainer({ logLevel, imageName: "hyperledger/cactus-connector-corda-server", - imageVersion: "2021-03-24-feat-620", + imageVersion: "2021-03-25-feat-622", // imageName: "cccs", // imageVersion: "latest", envVars: [envVarSpringAppJson], diff --git a/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/jvm-kotlin-spring-server.test.ts b/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/jvm-kotlin-spring-server.test.ts index 625ad4b090..a5a64a25f0 100644 --- a/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/jvm-kotlin-spring-server.test.ts +++ b/packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/jvm-kotlin-spring-server.test.ts @@ -79,7 +79,7 @@ test(testCase, async (t: Test) => { const connector = new CordaConnectorContainer({ logLevel, imageName: "hyperledger/cactus-connector-corda-server", - imageVersion: "2021-03-24-feat-620", + imageVersion: "2021-03-25-feat-622", // imageName: "cccs", // imageVersion: "latest", envVars: [envVarSpringAppJson],