-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(corda-connector): params factory pattern support #620
Primary change ============ Added support in the Corda ledger connector plugin's JVM backend to have the JSON DSL be able to express static and non-static factory functions. For the non-static factory functions, the invocation target can be any constructable object that the DLS can express via the `JvmObject` type. The method lookups are performed the same way as when looking up constructors but with the additional constraint that the name of the method has to also match not just the parameter types/count. Miscellaneous changes ================== Refactored ApiPluginLedgerConnectorCordaServiceImpl.kt so that it does not include the JSON DSL deserialization logic within itself but instead outsources all of that to a separate class that was newly added just for this: JsonJvmObjectDeserializer.kt Updated the tests to specify the new invocation parameters accordingly: The Currency class is now instantiated through the JSON DLS thanks to the static factory method support we just added. Published the container image to the DockerHub registry with the updated JVM corda connector plugin under the tag: hyperledger/cactus-connector-corda-server:2021-03-24-feat-620 (which is now used by both of the integration tests that we currently have for corda) The contract deployment request object will now allow a minimum of zero items in the deployment configuration array parameter which we needed to cover the case when a jar only needs to be deployed to the classpath of the connector plugin because it is already present on the Corda node's cordapps directory (meaning that adding it there again would make it impossible to start back up the corda node) Fixes #620 Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
- Loading branch information
Showing
11 changed files
with
265 additions
and
195 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
...hyperledger/cactus/plugin/ledger/connector/corda/server/impl/JsonJvmObjectDeserializer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package org.hyperledger.cactus.plugin.ledger.connector.corda.server.impl | ||
|
||
import net.corda.core.utilities.loggerFor | ||
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.JvmObject | ||
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.JvmTypeKind | ||
import org.xeustechnologies.jcl.JarClassLoader | ||
import java.lang.Exception | ||
import java.lang.IllegalStateException | ||
import java.lang.RuntimeException | ||
import java.lang.reflect.Constructor | ||
import java.lang.reflect.Method | ||
import java.util.* | ||
|
||
// 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? | ||
class JsonJvmObjectDeserializer( | ||
val jcl: JarClassLoader = JarClassLoader(JsonJvmObjectDeserializer::class.java.classLoader) | ||
) { | ||
|
||
companion object { | ||
val logger = loggerFor<JsonJvmObjectDeserializer>() | ||
|
||
// If something is missing from here that's because they also missed at in the documentation: | ||
// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html | ||
val exoticTypes: Map<String, Class<*>> = mapOf( | ||
|
||
"byte" to Byte::class.java, | ||
"char" to Char::class.java, | ||
"int" to Int::class.java, | ||
"short" to Short::class.java, | ||
"long" to Long::class.java, | ||
"float" to Float::class.java, | ||
"double" to Double::class.java, | ||
"boolean" to Boolean::class.java, | ||
|
||
"byte[]" to ByteArray::class.java, | ||
"char[]" to CharArray::class.java, | ||
"int[]" to IntArray::class.java, | ||
"short[]" to ShortArray::class.java, | ||
"long[]" to LongArray::class.java, | ||
"float[]" to FloatArray::class.java, | ||
"double[]" to DoubleArray::class.java, | ||
"boolean[]" to BooleanArray::class.java | ||
) | ||
} | ||
|
||
fun getOrInferType(fqClassName: String): Class<*> { | ||
Objects.requireNonNull(fqClassName, "fqClassName must not be null for its type to be inferred.") | ||
|
||
return if (exoticTypes.containsKey(fqClassName)) { | ||
exoticTypes.getOrElse( | ||
fqClassName, | ||
{ throw IllegalStateException("Could not locate Class<*> for $fqClassName Exotic JVM types map must have been modified on a concurrent threat.") }) | ||
} else { | ||
try { | ||
jcl.loadClass(fqClassName, true) | ||
} catch (ex: ClassNotFoundException) { | ||
Class.forName(fqClassName) | ||
} | ||
} | ||
} | ||
|
||
fun instantiate(jvmObject: JvmObject): Any? { | ||
logger.info("Instantiating ... JvmObject={}", jvmObject) | ||
|
||
val clazz = getOrInferType(jvmObject.jvmType.fqClassName) | ||
|
||
when (jvmObject.jvmTypeKind) { | ||
JvmTypeKind.REFERENCE -> { | ||
if (jvmObject.jvmCtorArgs == null) { | ||
throw IllegalArgumentException("jvmObject.jvmCtorArgs cannot be null when jvmObject.jvmTypeKind == JvmTypeKind.REFERENCE") | ||
} | ||
val constructorArgs: Array<Any?> = jvmObject.jvmCtorArgs.map { x -> instantiate(x) }.toTypedArray() | ||
|
||
when { | ||
List::class.java.isAssignableFrom(clazz) -> { | ||
return listOf(*constructorArgs) | ||
} | ||
Array<Any>::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) | ||
} | ||
jvmObject.jvmType.constructorName != null -> { | ||
val methodArgTypes: List<Class<*>> = | ||
jvmObject.jvmCtorArgs.map { x -> getOrInferType(x.jvmType.fqClassName) } | ||
val factoryMethod: Method | ||
try { | ||
factoryMethod = clazz.methods | ||
.filter { c -> c.name == jvmObject.jvmType.constructorName } | ||
.filter { c -> c.parameterCount == methodArgTypes.size } | ||
.single { c -> | ||
c.parameterTypes | ||
.mapIndexed { index, clazz -> clazz.isAssignableFrom(methodArgTypes[index]) } | ||
.all { x -> x } | ||
} | ||
} 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 availableMethods = | ||
"Searched among the ${clazz.constructors.size} available methods: $methodsAsStrings" | ||
throw RuntimeException("$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 | ||
} | ||
else -> { | ||
val constructorArgTypes: List<Class<*>> = | ||
jvmObject.jvmCtorArgs.map { x -> getOrInferType(x.jvmType.fqClassName) } | ||
val constructor: Constructor<*> | ||
try { | ||
constructor = clazz.constructors | ||
.filter { c -> c.parameterCount == constructorArgTypes.size } | ||
.single { c -> | ||
c.parameterTypes | ||
.mapIndexed { index, clazz -> clazz.isAssignableFrom(constructorArgTypes[index]) } | ||
.all { x -> x } | ||
} | ||
} catch (ex: NoSuchElementException) { | ||
val argTypes = jvmObject.jvmCtorArgs.joinToString(",") { x -> x.jvmType.fqClassName } | ||
val className = jvmObject.jvmType.fqClassName | ||
val constructorsAsStrings = clazz.constructors | ||
.mapIndexed { i, c -> "$className->Constructor#${i + 1}(${c.parameterTypes.joinToString { p -> p.name }})" } | ||
.joinToString(" ;; ") | ||
val targetConstructor = "Cannot find matching constructor for ${className}(${argTypes})" | ||
val availableConstructors = | ||
"Searched among the ${clazz.constructors.size} available constructors: $constructorsAsStrings" | ||
throw RuntimeException("$targetConstructor --- $availableConstructors") | ||
} | ||
|
||
logger.info("Constructor=${constructor}") | ||
constructorArgs.forEachIndexed { index, it -> logger.info("Constructor ARGS: #${index} -> $it") } | ||
val instance = constructor.newInstance(*constructorArgs) | ||
logger.info("Instantiated REFERENCE OK {}", instance) | ||
return instance | ||
} | ||
} | ||
|
||
} | ||
JvmTypeKind.PRIMITIVE -> { | ||
logger.info("Instantiated PRIMITIVE OK {}", jvmObject.primitiveValue) | ||
return jvmObject.primitiveValue | ||
} | ||
else -> { | ||
throw IllegalArgumentException("Unknown jvmObject.jvmTypeKind (${jvmObject.jvmTypeKind})") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.