From 20570d72cf7a0c72f56e563abb5b64d89d26dcea Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 3 May 2018 13:25:29 +0100 Subject: [PATCH 01/18] CORDA-1238 - Move blob inspector initial work into experimental (#3058) * CORDA-1238 - Initial blob inspector tool commit Note this is WIP and not ready for prime time but it's time it moved off of a personal branch and into the main code base, especially if I'm passing the serialization code onto someone else's shoulders * CORDA-1238 - Move blob inspector into experimental It was developed locally in tools (as it's a tool), but it's no where near production ready, so lets just ship it in experimental for now * CORDA-1238 - Tidyup and bug fixes --- .idea/compiler.xml | 4 +- build.gradle | 1 + experimental/blobinspector/build.gradle | 52 +++ .../net/corda/blobinspector/BlobInspector.kt | 399 ++++++++++++++++++ .../net/corda/blobinspector/BlobLoader.kt | 40 ++ .../kotlin/net/corda/blobinspector/Config.kt | 137 ++++++ .../kotlin/net/corda/blobinspector/Errors.kt | 3 + .../blobinspector/IndentingStringBuilder.kt | 45 ++ .../kotlin/net/corda/blobinspector/Main.kt | 81 ++++ .../net/corda/blobinspector/FileParseTests.kt | 84 ++++ .../net/corda/blobinspector/InMemoryTests.kt | 89 ++++ .../net/corda/blobinspector/ModeParse.kt | 77 ++++ .../corda/blobinspector/SimplifyClassTests.kt | 28 ++ .../blobinspector/FileParseTests.1Composite | Bin 0 -> 537 bytes .../corda/blobinspector/FileParseTests.1Int | Bin 0 -> 244 bytes .../blobinspector/FileParseTests.1String | Bin 0 -> 262 bytes .../blobinspector/FileParseTests.2Composite | Bin 0 -> 1004 bytes .../corda/blobinspector/FileParseTests.2Int | Bin 0 -> 284 bytes .../corda/blobinspector/FileParseTests.3Int | Bin 0 -> 317 bytes .../blobinspector/FileParseTests.IntList | Bin 0 -> 426 bytes .../blobinspector/FileParseTests.MapIntClass | Bin 0 -> 759 bytes .../blobinspector/FileParseTests.MapIntString | Bin 0 -> 474 bytes .../blobinspector/FileParseTests.StringList | Bin 0 -> 448 bytes .../net/corda/blobinspector/networkParams | Bin 0 -> 3066 bytes .../serialization/SerializationScheme.kt | 2 +- .../amqp/DeserializationInput.kt | 32 +- .../internal/serialization/amqp/Envelope.kt | 2 +- settings.gradle | 1 + 28 files changed, 1060 insertions(+), 17 deletions(-) create mode 100644 experimental/blobinspector/build.gradle create mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt create mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt create mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt create mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt create mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt create mode 100644 experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt create mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt create mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt create mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt create mode 100644 experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Int create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.IntList create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList create mode 100644 experimental/blobinspector/src/test/resources/net/corda/blobinspector/networkParams diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a28d15edcc..e2620b8b2d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,6 +14,8 @@ + + @@ -172,4 +174,4 @@ - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index f8270f2808..fe6a9308eb 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,7 @@ buildscript { ext.ghostdriver_version = '2.1.0' ext.eaagentloader_version = '1.0.3' ext.jsch_version = '0.1.54' + ext.commons_cli_version = '1.4' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' diff --git a/experimental/blobinspector/build.gradle b/experimental/blobinspector/build.gradle new file mode 100644 index 0000000000..2862ff6fae --- /dev/null +++ b/experimental/blobinspector/build.gradle @@ -0,0 +1,52 @@ +apply plugin: 'java' +apply plugin: 'kotlin' +apply plugin: 'application' + +mainClassName = 'net.corda.blobinspector.MainKt' + +dependencies { + compile project(':core') + compile project(':node-api') + + compile "commons-cli:commons-cli:$commons_cli_version" + + testCompile project(':test-utils') + + testCompile "junit:junit:$junit_version" +} + +/** + * To run from within gradle use + * + * ./gradlew -PrunArgs=" " :experimental:blobinspector:run + * + * For example, to parse a file from the command line and print out the deserialized properties + * + * ./gradlew -PrunArgs="-f -d" :experimental:blobinspector:run + * + * at the command line. + */ +run { + if (project.hasProperty('runArgs')) { + args = [ project.findProperty('runArgs').toString().split(" ") ].flatten() + } + + if (System.properties.getProperty('consoleLogLevel') != null) { + logging.captureStandardOutput(LogLevel.valueOf(System.properties.getProperty('consoleLogLevel'))) + logging.captureStandardError(LogLevel.valueOf(System.properties.getProperty('consoleLogLevel'))) + systemProperty "consoleLogLevel", System.properties.getProperty('consoleLogLevel') + } +} + +/** + * Build a executable jar + */ +jar { + baseName 'blobinspector' + manifest { + attributes( + 'Automatic-Module-Name': 'net.corda.experimental.blobinspector', + 'Main-Class': 'net.corda.blobinspector.MainKt' + ) + } +} diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt new file mode 100644 index 0000000000..8a30c2319f --- /dev/null +++ b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt @@ -0,0 +1,399 @@ +package net.corda.blobinspector + +import net.corda.core.crypto.SecureHash +import net.corda.core.serialization.EncodingWhitelist +import net.corda.core.serialization.SerializationEncoding +import net.corda.core.utilities.ByteSequence +import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl +import net.corda.nodeapi.internal.serialization.amqp.* +import org.apache.qpid.proton.amqp.Binary +import org.apache.qpid.proton.amqp.DescribedType +import org.apache.qpid.proton.amqp.Symbol + +/** + * Print a string to the console only if the verbose config option is set. + */ +fun String.debug(config: Config) { + if (config.verbose) { + println(this) + } +} + +/** + * + */ +interface Stringify { + fun stringify(sb: IndentingStringBuilder) +} + +/** + * Makes classnames easier to read by stripping off the package names from the class and separating nested + * classes + * + * For example: + * + * net.corda.blobinspector.Class1 + * Class1 + * + * net.corda.blobinspector.Class1 + * Class1 + * + * net.corda.blobinspector.Class1> + * Class1 > + * + * net.corda.blobinspector.Class1> + * Class1 :: C > + */ +fun String.simplifyClass(): String { + + return if (this.endsWith('>')) { + val templateStart = this.indexOf('<') + val clazz = (this.substring(0, templateStart)) + val params = this.substring(templateStart+1, this.length-1).split(',').map { it.simplifyClass() }.joinToString() + + "${clazz.simplifyClass()} <$params>" + } + else { + substring(this.lastIndexOf('.') + 1).replace("$", " :: ") + } +} + +/** + * Represents the deserialized form of the property of an Object + * + * @param name + * @param type + */ +abstract class Property( + val name: String, + val type: String) : Stringify + +/** + * Derived class of [Property], represents properties of an object that are non compelex, such + * as any POD type or String + */ +class PrimProperty( + name: String, + type: String, + private val value: String) : Property(name, type) { + override fun toString(): String = "$name : $type : $value" + + override fun stringify(sb: IndentingStringBuilder) { + sb.appendln("$name : $type : $value") + } +} + +/** + * Derived class of [Property] that represents a binary blob. Specifically useful because printing + * a stream of bytes onto the screen isn't very use friendly + */ +class BinaryProperty( + name: String, + type: String, + val value: ByteArray) : Property(name, type) { + override fun toString(): String = "$name : $type : <<>>" + + override fun stringify(sb: IndentingStringBuilder) { + sb.appendln("$name : $type : <<>>") + } +} + +/** + * Derived class of [Property] that represent a list property. List could be either PoD types or + * composite types. + */ +class ListProperty( + name: String, + type: String, + private val values: MutableList = mutableListOf()) : Property(name, type) { + override fun stringify(sb: IndentingStringBuilder) { + sb.apply { + if (values.isEmpty()) { + appendln("$name : $type : [ << EMPTY LIST >> ]") + } else if (values.first() is Stringify) { + appendln("$name : $type : [") + values.forEach { + (it as Stringify).stringify(this) + } + appendln("]") + } else { + appendln("$name : $type : [") + values.forEach { + appendln(it.toString()) + } + appendln("]") + } + } + } +} + +class MapProperty( + name: String, + type: String, + private val map: MutableMap<*, *> +) : Property(name, type) { + override fun stringify(sb: IndentingStringBuilder) { + if (map.isEmpty()) { + sb.appendln("$name : $type : { << EMPTY MAP >> }") + return + } + + // TODO this will not produce pretty output + sb.apply { + appendln("$name : $type : {") + map.forEach { + try { + (it.key as Stringify).stringify(this) + } catch (e: ClassCastException) { + append (it.key.toString() + " : ") + } + try { + (it.value as Stringify).stringify(this) + } catch (e: ClassCastException) { + appendln("\"${it.value.toString()}\"") + } + } + appendln("}") + } + } +} + +/** + * Derived class of [Property] that represents class properties that are themselves instances of + * some complex type. + */ +class InstanceProperty( + name: String, + type: String, + val value: Instance) : Property(name, type) { + override fun stringify(sb: IndentingStringBuilder) { + sb.append("$name : ") + value.stringify(sb) + } +} + +/** + * Represents an instance of a composite type. + */ +class Instance( + val name: String, + val type: String, + val fields: MutableList = mutableListOf()) : Stringify { + override fun stringify(sb: IndentingStringBuilder) { + sb.apply { + appendln("${name.simplifyClass()} : {") + fields.forEach { + it.stringify(this) + } + appendln("}") + } + } +} + +/** + * + */ +fun inspectComposite( + config: Config, + typeMap: Map, + obj: DescribedType): Instance { + if (obj.described !is List<*>) throw MalformedBlob("") + + val name = (typeMap[obj.descriptor] as CompositeType).name + "composite: $name".debug(config) + + val inst = Instance( + typeMap[obj.descriptor]?.name ?: "", + typeMap[obj.descriptor]?.label ?: "") + + (typeMap[obj.descriptor] as CompositeType).fields.zip(obj.described as List<*>).forEach { + " field: ${it.first.name}".debug(config) + inst.fields.add( + if (it.second is DescribedType) { + " - is described".debug(config) + val d = inspectDescribed(config, typeMap, it.second as DescribedType) + + when (d) { + is Instance -> + InstanceProperty( + it.first.name, + it.first.type, + d) + is List<*> -> { + " - List".debug(config) + ListProperty( + it.first.name, + it.first.type, + d as MutableList) + } + is Map<*, *> -> { + MapProperty( + it.first.name, + it.first.type, + d as MutableMap<*, *>) + } + else -> { + " skip it".debug(config) + return@forEach + } + } + + } else { + " - is prim".debug(config) + when (it.first.type) { + // Note, as in the case of SHA256 we can treat particular binary types + // as different properties with a little coercion + "binary" -> { + if (name == "net.corda.core.crypto.SecureHash\$SHA256") { + PrimProperty( + it.first.name, + it.first.type, + SecureHash.SHA256((it.second as Binary).array).toString()) + } else { + BinaryProperty(it.first.name, it.first.type, (it.second as Binary).array) + } + } + else -> PrimProperty(it.first.name, it.first.type, it.second.toString()) + } + }) + } + + return inst +} + +fun inspectRestricted( + config: Config, + typeMap: Map, + obj: DescribedType): Any { + return when ((typeMap[obj.descriptor] as RestrictedType).source) { + "list" -> inspectRestrictedList(config, typeMap, obj) + "map" -> inspectRestrictedMap(config, typeMap, obj) + else -> throw NotImplementedError() + } +} + + +fun inspectRestrictedList( + config: Config, + typeMap: Map, + obj: DescribedType +) : List { + if (obj.described !is List<*>) throw MalformedBlob("") + + return mutableListOf().apply { + (obj.described as List<*>).forEach { + when (it) { + is DescribedType -> add(inspectDescribed(config, typeMap, it)) + is RestrictedType -> add(inspectRestricted(config, typeMap, it)) + else -> add (it.toString()) + } + } + } +} + +fun inspectRestrictedMap( + config: Config, + typeMap: Map, + obj: DescribedType +) : Map { + if (obj.described !is Map<*,*>) throw MalformedBlob("") + + return mutableMapOf().apply { + (obj.described as Map<*, *>).forEach { + val key = when (it.key) { + is DescribedType -> inspectDescribed(config, typeMap, it.key as DescribedType) + is RestrictedType -> inspectRestricted(config, typeMap, it.key as RestrictedType) + else -> it.key.toString() + } + + val value = when (it.value) { + is DescribedType -> inspectDescribed(config, typeMap, it.value as DescribedType) + is RestrictedType -> inspectRestricted(config, typeMap, it.value as RestrictedType) + else -> it.value.toString() + } + + this[key] = value + } + } +} + + +/** + * Every element of the blob stream will be a ProtonJ [DescribedType]. When inspecting the blob stream + * the two custom Corda types we're interested in are [CompositeType]'s, representing the instance of + * some object (class), and [RestrictedType]'s, representing containers and enumerations. + * + * @param config The configuration object that controls the behaviour of the BlobInspector + * @param typeMap + * @param obj + */ +fun inspectDescribed( + config: Config, + typeMap: Map, + obj: DescribedType): Any { + "${obj.descriptor} in typeMap? = ${obj.descriptor in typeMap}".debug(config) + + return when (typeMap[obj.descriptor]) { + is CompositeType -> { + "* It's composite".debug(config) + inspectComposite(config, typeMap, obj) + } + is RestrictedType -> { + "* It's restricted".debug(config) + inspectRestricted(config, typeMap, obj) + } + else -> { + "${typeMap[obj.descriptor]?.name} is neither Composite or Restricted".debug(config) + } + } + +} + +internal object NullEncodingWhitelist : EncodingWhitelist { + override fun acceptEncoding(encoding: SerializationEncoding) = false +} + +// TODO : Refactor to generically poerate on arbitrary blobs, not a single workflow +fun inspectBlob(config: Config, blob: ByteArray) { + val bytes = ByteSequence.of(blob) + + val headerSize = SerializationFactoryImpl.magicSize + + // TODO written to only understand one version, when we support multiple this will need to change + val headers = listOf(ByteSequence.of(amqpMagic.bytes)) + + val blobHeader = bytes.take(headerSize) + + if (blobHeader !in headers) { + throw MalformedBlob("Blob is not a Corda AMQP serialised object graph") + } + + + val e = DeserializationInput.getEnvelope(bytes, NullEncodingWhitelist) + + if (config.schema) { + println(e.schema) + } + + if (config.transforms) { + println(e.transformsSchema) + } + + val typeMap = e.schema.types.associateBy({ it.descriptor.name }, { it }) + + if (config.data) { + val inspected = inspectDescribed(config, typeMap, e.obj as DescribedType) + + println("\n${IndentingStringBuilder().apply { (inspected as Instance).stringify(this) }}") + + (inspected as Instance).fields.find { + it.type.startsWith("net.corda.core.serialization.SerializedBytes<") + }?.let { + "Found field of SerializedBytes".debug(config) + (it as InstanceProperty).value.fields.find { it.name == "bytes" }?.let { raw -> + inspectBlob(config, (raw as BinaryProperty).value) + } + } + } +} + diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt new file mode 100644 index 0000000000..a027249079 --- /dev/null +++ b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobLoader.kt @@ -0,0 +1,40 @@ +package net.corda.blobinspector + +import java.io.File +import java.net.URL + +/** + * + */ +class FileBlobHandler(config_: Config) : BlobHandler(config_) { + private val path = File(URL((config_ as FileConfig).file).toURI()) + + override fun getBytes(): ByteArray { + return path.readBytes() + } +} + +/** + * + */ +class InMemoryBlobHandler(config_: Config) : BlobHandler(config_) { + private val localBytes = (config_ as InMemoryConfig).blob?.bytes ?: kotlin.ByteArray(0) + override fun getBytes(): ByteArray = localBytes +} + +/** + * + */ +abstract class BlobHandler (val config: Config) { + companion object { + fun make(config: Config) : BlobHandler { + return when (config.mode) { + Mode.file -> FileBlobHandler(config) + Mode.inMem -> InMemoryBlobHandler(config) + } + } + } + + abstract fun getBytes() : ByteArray +} + diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt new file mode 100644 index 0000000000..376331ec2b --- /dev/null +++ b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Config.kt @@ -0,0 +1,137 @@ +package net.corda.blobinspector + +import org.apache.commons.cli.CommandLine +import net.corda.core.serialization.SerializedBytes +import org.apache.commons.cli.Option +import org.apache.commons.cli.Options + +/** + * Enumeration of the modes in which the blob inspector can be run. + * + * @property make lambda function that takes no parameters and returns a specific instance of the configuration + * object for that mode. + * + * @property options A lambda function that takes no parameters and returns an [Options] instance that define + * the command line flags related to this mode. For example ``file`` mode would have an option to pass in + * the name of the file to read. + * + */ +enum class Mode( + val make : () -> Config, + val options : (Options) -> Unit +) { + file( + { + FileConfig(Mode.file) + }, + { o -> + o.apply{ + addOption( + Option ("f", "file", true, "path to file").apply { + isRequired = true + } + ) + } + } + ), + inMem( + { + InMemoryConfig(Mode.inMem) + }, + { + // The in memory only mode has no specific option assocaited with it as it's intended for + // testing purposes only within the unit test framework and not use on the command line + } + ) +} + +/** + * Configuration data class for the Blob Inspector. + * + * @property mode + */ +abstract class Config (val mode: Mode) { + var schema: Boolean = false + var transforms: Boolean = false + var data: Boolean = false + var verbose: Boolean = false + + abstract fun populateSpecific(cmdLine: CommandLine) + abstract fun withVerbose() : Config + + fun populate(cmdLine: CommandLine) { + schema = cmdLine.hasOption('s') + transforms = cmdLine.hasOption('t') + data = cmdLine.hasOption('d') + verbose = cmdLine.hasOption('v') + + populateSpecific(cmdLine) + } + + fun options() = Options().apply { + // install generic options + addOption(Option("s", "schema", false, "print the blob's schema").apply { + isRequired = false + }) + + addOption(Option("t", "transforms", false, "print the blob's transforms schema").apply { + isRequired = false + }) + + addOption(Option("d", "data", false, "Display the serialised data").apply { + isRequired = false + }) + + addOption(Option("v", "verbose", false, "Enable debug output").apply { + isRequired = false + }) + + // install the mode specific options + mode.options(this) + } +} + + +/** + * Configuration object when running in "File" mode, i.e. the object has been specified at + * the command line + */ +class FileConfig ( + mode: Mode +) : Config(mode) { + + var file: String = "unset" + + override fun populateSpecific(cmdLine : CommandLine) { + file = cmdLine.getParsedOptionValue("f") as String + } + + override fun withVerbose() : FileConfig { + return FileConfig(mode).apply { + this.schema = schema + this.transforms = transforms + this.data = data + this.verbose = true + } + } +} + + +/** + * Placeholder config objet used when running unit tests and the inspected blob is being fed in + * via some mechanism directly. Normally this will be the direct serialisation of an object in a unit + * test and then dumping that blob into the inspector for visual comparison of the output + */ +class InMemoryConfig ( + mode: Mode +) : Config(mode) { + var blob: SerializedBytes<*>? = null + + override fun populateSpecific(cmdLine: CommandLine) { + throw UnsupportedOperationException("In memory config is for testing only and cannot set specific flags") + } + + override fun withVerbose(): Config { + throw UnsupportedOperationException("In memory config is for testing headlessly, cannot be verbose") + } +} diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt new file mode 100644 index 0000000000..888ef1e302 --- /dev/null +++ b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Errors.kt @@ -0,0 +1,3 @@ +package net.corda.blobinspector + +class MalformedBlob(msg: String) : Exception(msg) diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt new file mode 100644 index 0000000000..48d81a8eb7 --- /dev/null +++ b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/IndentingStringBuilder.kt @@ -0,0 +1,45 @@ +package net.corda.blobinspector + +/** + * Wrapper around a [StringBuilder] that automates the indenting of lines as they're appended to facilitate + * pretty printing of deserialized blobs. + * + * @property sb The wrapped [StringBuilder] + * @property indenting Boolean flag that indicates weather we need to pad the start of whatever text + * currently being added to the string. + * @property indent How deeply the next line should be offset from the first column + */ +class IndentingStringBuilder(s : String = "", private val offset : Int = 4) { + private val sb = StringBuilder(s) + private var indenting = true + private var indent = 0 + + private fun wrap(ln: String, appender: (String) -> Unit) { + if ((ln.endsWith("}") || ln.endsWith("]")) && indent > 0 && ln.length == 1) { + indent -= offset + } + + appender(ln) + + if (ln.endsWith("{") || ln.endsWith("[")){ + indent += offset + } + } + + fun appendln(ln: String) { + wrap(ln) { s -> sb.appendln("${"".padStart(if (indenting) indent else 0, ' ')}$s") } + + indenting = true + } + + + fun append(ln: String) { + indenting = false + + wrap(ln) { s -> sb.append("${"".padStart(indent, ' ')}$s") } + } + + override fun toString(): String { + return sb.toString() + } +} \ No newline at end of file diff --git a/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt new file mode 100644 index 0000000000..0e13b9e087 --- /dev/null +++ b/experimental/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt @@ -0,0 +1,81 @@ +package net.corda.blobinspector + +import org.apache.commons.cli.* +import java.lang.IllegalArgumentException + +/** + * Mode isn't a required property as we default it to [Mode.file] + */ +private fun modeOption() = Option("m", "mode", true, "mode, file is the default").apply { + isRequired = false +} + +/** + * + * Parse the command line arguments looking for the main mode into which the application is + * being put. Note, this defaults to [Mode.file] if not set meaning we will look for a file path + * being passed as a parameter and parse that file. + * + * @param args reflects the command line arguments + * + * @return An instantiated but unpopulated [Config] object instance suitable for the mode into + * which we've been placed. This Config object should be populated via [loadModeSpecificOptions] + */ +fun getMode(args: Array) : Config { + // For now we only care what mode we're being put in, we can build the rest of the args and parse them + // later + val options = Options().apply { + addOption(modeOption()) + } + + val cmd = try { + DefaultParser().parse(options, args, true) + } catch (e: org.apache.commons.cli.ParseException) { + println (e) + HelpFormatter().printHelp("blobinspector", options) + throw IllegalArgumentException("OH NO!!!") + } + + return try { + Mode.valueOf(cmd.getParsedOptionValue("m") as? String ?: "file") + } catch (e: IllegalArgumentException) { + Mode.file + }.make() +} + +/** + * + * @param config an instance of a [Config] specialisation suitable for the mode into which + * the application has been put. + * @param args The command line arguments + */ +fun loadModeSpecificOptions(config: Config, args: Array) { + config.apply { + // load that modes specific command line switches, needs to include the mode option + val modeSpecificOptions = config.options().apply { + addOption(modeOption()) + } + + populate (try { + DefaultParser().parse(modeSpecificOptions, args, false) + } catch (e: org.apache.commons.cli.ParseException) { + println ("Error: ${e.message}") + HelpFormatter().printHelp("blobinspector", modeSpecificOptions) + System.exit(1) + return + }) + } +} + +/** + * Executable entry point + */ +fun main(args: Array) { + println ("<<< WARNING: this tool is experimental and under active development >>>") + getMode(args).let { mode -> + loadModeSpecificOptions(mode, args) + BlobHandler.make(mode) + }.apply { + inspectBlob(config, getBytes()) + } +} diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt new file mode 100644 index 0000000000..a018baaf49 --- /dev/null +++ b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/FileParseTests.kt @@ -0,0 +1,84 @@ +package net.corda.blobinspector + +import java.net.URI + +import org.junit.Test +import net.corda.testing.common.internal.ProjectStructure.projectRootDir + + +class FileParseTests { + @Suppress("UNUSED") + var localPath : URI = projectRootDir.toUri().resolve( + "tools/blobinspector/src/test/resources/net/corda/blobinspector") + + fun setupArgsWithFile(path: String) = Array(5) { + when (it) { + 0 -> "-m" + 1 -> "file" + 2 -> "-f" + 3 -> path + 4 -> "-d" + else -> "error" + } + } + + private val filesToTest = listOf ( + "FileParseTests.1Int", + "FileParseTests.2Int", + "FileParseTests.3Int", + "FileParseTests.1String", + "FileParseTests.1Composite", + "FileParseTests.2Composite", + "FileParseTests.IntList", + "FileParseTests.StringList", + "FileParseTests.MapIntString", + "FileParseTests.MapIntClass" + ) + + fun testFile(file : String) { + val path = FileParseTests::class.java.getResource(file) + val args = setupArgsWithFile(path.toString()) + + val handler = getMode(args).let { mode -> + loadModeSpecificOptions(mode, args) + BlobHandler.make(mode) + } + + inspectBlob(handler.config, handler.getBytes()) + } + + @Test + fun simpleFiles() { + filesToTest.forEach { testFile(it) } + } + + @Test + fun specificTest() { + testFile(filesToTest[4]) + testFile(filesToTest[5]) + testFile(filesToTest[6]) + } + + @Test + fun networkParams() { + val file = "networkParams" + val path = FileParseTests::class.java.getResource(file) + val verbose = false + + val args = verbose.let { + if (it) + Array(4) { when (it) { 0 -> "-f" ; 1 -> path.toString(); 2 -> "-d"; 3 -> "-vs"; else -> "error" } } + else + Array(3) { when (it) { 0 -> "-f" ; 1 -> path.toString(); 2 -> "-d"; else -> "error" } } + } + + val handler = getMode(args).let { mode -> + loadModeSpecificOptions(mode, args) + BlobHandler.make(mode) + } + + inspectBlob(handler.config, handler.getBytes()) + + } + +} diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt new file mode 100644 index 0000000000..d8451df92d --- /dev/null +++ b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt @@ -0,0 +1,89 @@ +package net.corda.blobinspector + +import net.corda.core.serialization.SerializedBytes +import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import org.junit.Test + + +class InMemoryTests { + private val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + + private fun inspect (b: SerializedBytes<*>) { + BlobHandler.make( + InMemoryConfig(Mode.inMem).apply { blob = b; data = true} + ).apply { + inspectBlob(config, getBytes()) + } + } + + @Test + fun test1() { + data class C (val a: Int, val b: Long, val c: String) + inspect (SerializationOutput(factory).serialize(C(100, 567L, "this is a test"))) + } + + @Test + fun test2() { + data class C (val i: Int, val c: C?) + inspect (SerializationOutput(factory).serialize(C(1, C(2, C(3, C(4, null)))))) + } + + @Test + fun test3() { + data class C (val a: IntArray, val b: Array) + + val a = IntArray(10) { i -> i } + val c = C(a, arrayOf("aaa", "bbb", "ccc")) + + inspect (SerializationOutput(factory).serialize(c)) + } + + @Test + fun test4() { + data class Elem(val e1: Long, val e2: String) + data class Wrapper (val name: String, val elementes: List) + + inspect (SerializationOutput(factory).serialize( + Wrapper("Outer Class", + listOf( + Elem(1L, "First element"), + Elem(2L, "Second element"), + Elem(3L, "Third element") + )))) + } + + @Test + fun test4b() { + data class Elem(val e1: Long, val e2: String) + data class Wrapper (val name: String, val elementes: List>) + + inspect (SerializationOutput(factory).serialize( + Wrapper("Outer Class", + listOf ( + listOf( + Elem(1L, "First element"), + Elem(2L, "Second element"), + Elem(3L, "Third element") + ), + listOf( + Elem(4L, "Fourth element"), + Elem(5L, "Fifth element"), + Elem(6L, "Sixth element") + ) + )))) + } + + @Test + fun test5() { + data class C (val a: Map) + + inspect (SerializationOutput(factory).serialize( + C(mapOf( + "a" to "a a a", + "b" to "b b b", + "c" to "c c c")) + )) + } +} \ No newline at end of file diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt new file mode 100644 index 0000000000..9b69363386 --- /dev/null +++ b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/ModeParse.kt @@ -0,0 +1,77 @@ +package net.corda.blobinspector + +import org.junit.Test +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import kotlin.test.assertFalse + +class ModeParse { + @Test + fun fileIsSetToFile() { + val opts1 = Array(2) { + when (it) { + 0 -> "-m" + 1 -> "file" + else -> "error" + } + } + + assertEquals(Mode.file, getMode(opts1).mode) + } + + @Test + fun nothingIsSetToFile() { + val opts1 = Array(0) { "" } + + assertEquals(Mode.file, getMode(opts1).mode) + } + + @Test + fun filePathIsSet() { + val opts1 = Array(4) { + when (it) { + 0 -> "-m" + 1 -> "file" + 2 -> "-f" + 3 -> "path/to/file" + else -> "error" + } + } + + val config = getMode(opts1) + assertTrue (config is FileConfig) + assertEquals(Mode.file, config.mode) + assertEquals("unset", (config as FileConfig).file) + + loadModeSpecificOptions(config, opts1) + + assertEquals("path/to/file", config.file) + } + + @Test + fun schemaIsSet() { + Array(2) { when (it) { 0 -> "-f"; 1 -> "path/to/file"; else -> "error" } }.let { options -> + getMode(options).apply { + loadModeSpecificOptions(this, options) + assertFalse (schema) + } + } + + Array(3) { when (it) { 0 -> "--schema"; 1 -> "-f"; 2 -> "path/to/file"; else -> "error" } }.let { + getMode(it).apply { + loadModeSpecificOptions(this, it) + assertTrue (schema) + } + } + + Array(3) { when (it) { 0 -> "-f"; 1 -> "path/to/file"; 2 -> "-s"; else -> "error" } }.let { + getMode(it).apply { + loadModeSpecificOptions(this, it) + assertTrue (schema) + } + } + + } + + +} \ No newline at end of file diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt new file mode 100644 index 0000000000..10d470685b --- /dev/null +++ b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/SimplifyClassTests.kt @@ -0,0 +1,28 @@ +package net.corda.blobinspector + +import org.junit.Test + +class SimplifyClassTests { + + @Test + fun test1() { + data class A(val a: Int) + + println (A::class.java.name) + println (A::class.java.name.simplifyClass()) + } + + @Test + fun test2() { + val p = this.javaClass.`package`.name + + println("$p.Class1<$p.Class2>") + println("$p.Class1<$p.Class2>".simplifyClass()) + println("$p.Class1<$p.Class2, $p.Class3>") + println("$p.Class1<$p.Class2, $p.Class3>".simplifyClass()) + println("$p.Class1<$p.Class2<$p.Class3>>") + println("$p.Class1<$p.Class2<$p.Class3>>".simplifyClass()) + println("$p.Class1<$p.Class2<$p.Class3>>") + println("$p.Class1\$C<$p.Class2<$p.Class3>>".simplifyClass()) + } +} \ No newline at end of file diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite new file mode 100644 index 0000000000000000000000000000000000000000..450e6970da613f518ebafb1bdf71cb8c212f4a05 GIT binary patch literal 537 zcmYe!FG@*dWME)uIGO|`85kHZFfcGN0U68;i4ypq(Syu=*6;?$zd#GK5k#FEVXJiWx+ z!UDaJ)Z!9D=bXgiVh2~aLCgo#n27O&!vTLrxF(hZPV5UA6BnA3?f@gtypm#92M0%l wKUoi~WF^}uJYK;S&JG7G5dq0^KmhC&W?;y5b13o`XG^GZ^S@)C3Oic^a+ z6LT`F5=%1k^YjvP3k&o@Qj1FrJ@ZN&T;awrA5dc=#xV{DWD$0=91vh%$e6g08EBd7 QLPi4zM<=*}oCg^h04>={1poj5 literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String new file mode 100644 index 0000000000000000000000000000000000000000..9676f0375fb488e6ec107de206caa34f9cc19f96 GIT binary patch literal 262 zcmYe!FG@*dWME)uIGO|`85kH3d}3x;tdy5pqL&Pkv+{H_c1zbUjtomPFg6M^G4%>b zEwga6wLKunxR5U-BePfmh!Pb_Qj1IAhARf#2;`FVPYxrGIKAwUBSgG-7s^U@t$;U+O3P-7y-bq)vQ e5Z1FC5M*D-n7ELw7;K!YgM*_J+(gcUj0^y)B2T9P literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite new file mode 100644 index 0000000000000000000000000000000000000000..0bf3a5c47551253abc26f5aa0b23a67d9549e7b2 GIT binary patch literal 1004 zcmbtTK~KUk7;VRf@#eu8y?Wpv$y5jeM$87xC=r!_LA{kRurM~Hm4PRJf`4J*Ieb=9$#yG2LQeS5PIi_5EyM*29 z3H6-_+==k{ZAtjx2hp!dyaw0#mE@*V-EUGiin7f^6wQKBh|nw?-f=4>@L^LMu0Q=VIy z_OR6qa!Tx3T3FtkcxFwB!qSuRtc>Fc@pfn~+zFcXYBl$#+F11eQkWxs*3c>}4kvmM w8B2#-Yy-M$E!0QyJ;kQ%1${uqNgoEf7iN|4OrSP3+0)ZD>w`?#Sbl-v3;zg0-v9sr literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int new file mode 100644 index 0000000000000000000000000000000000000000..118a23f37b86208a302c0d3be55efaf86673c3d3 GIT binary patch literal 284 zcmYe!FG@*dWME)uIGO|`85kHZ05L0&!OXB&DKE7|FBzo5D%mMHEj!9ItvIbB(#s{# zsKV6OT;I{w_5cS{NJ=44`3blQOb2!{9@xeR=d&JI$-2-SZj@eLeoAU$L8e}2UP)?E zUSf`3acWU!VoqjNVo7Fxo?c>ZVS!#qYH^8?XI@FMgDcz|<^yU>#5l>}fF%=L6UzYs d_Jxdz3z>noxh`ZhaBy@&lS{%X$9a&E0RTlfQnUa7 literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int new file mode 100644 index 0000000000000000000000000000000000000000..9f00d5906853f44dcc3d7aecab4b8b5babbfbae4 GIT binary patch literal 317 zcmYe!FG@*dWME)uIGO|`85kHZ0I?d7!OXB&DKE7|FBzo5s@%0S(#A+RS1D6=#eAWX;Sr?ka&C|=vPf1NI$kfZs zD@iTNOU%(LPA$qz%*m`uEXmBz(@V@PEYJ%{EiN(k%quB&aD|)0d_awf7ZWQwYH6~&_<8Z)$5w3~lfHM0+#>9n;S_i}!7Yb)3 zmL=+ymSpDWfy}o7y28%E!O;n>ob5m$8+KK$3t4i2THq?tU4tvgkUYS7kdXlZo6mpj literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass new file mode 100644 index 0000000000000000000000000000000000000000..175949d9aab8cdbb3b545e805a3b620ef6ab854e GIT binary patch literal 759 zcmYe!FG@*dWME)uIGO|`85kHZFfcGZ05X^v7AxhYmgpseR9KZ{dbos^Iad?~1O)n; zM&zYr78e$m+u9xoXGBp~&_q_IkiQ{+;0|H}Imij-GE1<_V2))y@PKupE!>rQdHE@+ zi3OQ@nRz9tMR|!idc~Rf#2;`FVPYxrGIKA*sbBzKI2%c_q#{iN(bZu5g=} z52!H_<7bBhS&VQ^EC*uQ7c%E27A$1cI^f5+&^s%!EK#qtBr``3Xp0TdB{~YEJJ1N| zyka{C2S+EkMQjH;*r;o|E7;v|3(&ofD=LvfjP<}u)`jL|dJoAQbgOZt4Tl4kOmO$I f91vh%$e6g085qW{3mFZN0}CRTgjJ67AR_|+;<@xA literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString new file mode 100644 index 0000000000000000000000000000000000000000..67ba352ec48a9e7a47412b7580915babc19fa383 GIT binary patch literal 474 zcmYe!FG@*dWME)uIGO|`85kHZ0P$fUgPCEmQeJ9_UNT69l|^x&n{k1qsc%I@h^uR% zk4I*jS5&#J?STMB6m`BXxrS*bMHQ8v7NOzBu8!tz-X2NmwzdZ~*g`}WvL-43VTi^; zj-(_7FbZK`$XAk)S*!p=i3%mD#U*f;Fo9g619Orxkii6(V?D5eb)g;Hd3t&IDXEDC znR=OdC8$f$KdmT{p}R$^JAUTH~Yjvmk&8=zBk6pFz%**Q2k fI>Gg^9SCQ`tJoE6DqKCf<8TErl7Bc4GBN-FYQBy% literal 0 HcmV?d00001 diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList new file mode 100644 index 0000000000000000000000000000000000000000..5758d9fa62f3e815bac4b1ba1e6af87c07f22e02 GIT binary patch literal 448 zcmYe!FG@*dWME)uIGO|`85kHZ0P#v7gPCEmQeJ9_UNT69m8)aEV|Z|QcBn;8R+@HY zZenGHMM=7??Ewcy6m51}u@Al?FG5Vr7}RJ&q{f&iQ4+(8C{bffG=Zr9Y;`-kI)#StGM(=E z=D&Ua_y0#T%nb4gg7C~oX?O~PZ%Gi=Uxt@}u-Hg3lQjqNfa7^fqH= zSKK_1-LnVK(se-ltI#vFYdWv0X5T_vegMyQV5`u*|KKvZ-f?mYK3i9|TwkQQ zbDb0OXl&k^=uh^yCJr4?2ZoxLefwg74#lfujppZKLlZND(PYQ%2-aYjV1+yQDj~hLEE04I>6aW~pHen{!0NJd`S!M4MK)zdtH0Zd)n! z44Ol-g*7;4C3&=v)`>QVOD7*|+1Vn70!=s{*e0C+>yJa%qd%Wl>pC{E_tIZZUYaa) z`zNn$FzPOTcjgNzd;}Q$zWfjtAE+_TNDj*}YB5BaE&3BeT!{NB9pDE}#it#XoR@KU z5i4gR{N#Jb`H6Q_jLOu=V3a2^p$wZ8=`tJ@4Rcl=S^hhUOq9cLW+`gLRKR}N@9r=! zmKDckuS0X?)1bXW;`inVCAmUwR7Y&eFmvNzo*0w|APSZHT9m%9>w~z&>!?Ej!4~)J zv=txUQ!aFY5nUc47VEj0e99EY=#5c%V4D!A+GT8CabR&3(0L>aW3p#?|=QwFl)TUVUJ7{c~p;<1a4MWF>s$85xx`o}XAljdcH?Y1+Dqf>Tv5&e_X_t7Af~5EkuQd9d zsh#X>X&C`nZ8}hibw-gwDlk_ZHEi+m%^qW?s=dnckCf$qbDmd8)oWTR_GP!yw?OGN zBbPI%lL-%5%!Ifhqp(}v4HDmIm{~-L1r!-HK?(E1J_w?6V_mqak#QgNi*9H@JM_zn z-zv-|P#cU<<{8*#iA$PMpv?5JvF-!dfbKb%jx{G^bpPl^;&^4$q!_y>wyk z@6VyDXDWF6Lej}HM?U~lGYd@7^aTXV0 zu+C&Bi!pqpfT@OiD>bj3l3sT*#co{L{XyI5=(JN79!e7m7na(jPK&n`-|b5zu2tMEkn9R#I~VKfKPqv5dQD^chAcWm=Wk|j B^OXPq literal 0 HcmV?d00001 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 369978bb62..fa67d13c09 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -80,7 +80,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe open class SerializationFactoryImpl : SerializationFactory() { companion object { - private val magicSize = sequenceOf(kryoMagic, amqpMagic).map { it.size }.distinct().single() + val magicSize = sequenceOf(kryoMagic, amqpMagic).map { it.size }.distinct().single() } private val creator: List = Exception().stackTrace.asList() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index 04c85e4926..fdfaf01d81 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -35,7 +35,7 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) { private val objectHistory: MutableList = mutableListOf() - internal companion object { + companion object { private val BYTES_NEEDED_TO_PEEK: Int = 23 fun peekSize(bytes: ByteArray): Int { @@ -60,7 +60,7 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto @VisibleForTesting @Throws(NotSerializableException::class) - internal fun withDataBytes(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist, task: (ByteBuffer) -> T): T { + fun withDataBytes(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist, task: (ByteBuffer) -> T): T { // Check that the lead bytes match expected header val amqpSequence = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.") var stream: InputStream = ByteBufferInputStream(amqpSequence) @@ -79,8 +79,22 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto stream.close() } } + + @Throws(NotSerializableException::class) + fun getEnvelope(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist): Envelope { + return withDataBytes(byteSequence, encodingWhitelist) { dataBytes -> + val data = Data.Factory.create() + val expectedSize = dataBytes.remaining() + if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data") + Envelope.get(data) + } + } } + + @Throws(NotSerializableException::class) + fun getEnvelope(byteSequence: ByteSequence) = Companion.getEnvelope(byteSequence, encodingWhitelist) + @Throws(NotSerializableException::class) inline fun deserialize(bytes: SerializedBytes): T = deserialize(bytes, T::class.java) @@ -88,16 +102,6 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto inline internal fun deserializeAndReturnEnvelope(bytes: SerializedBytes): ObjectAndEnvelope = deserializeAndReturnEnvelope(bytes, T::class.java) - @Throws(NotSerializableException::class) - internal fun getEnvelope(byteSequence: ByteSequence): Envelope { - return withDataBytes(byteSequence, encodingWhitelist) { dataBytes -> - val data = Data.Factory.create() - val expectedSize = dataBytes.remaining() - if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data") - Envelope.get(data) - } - } - @Throws(NotSerializableException::class) private fun des(generator: () -> R): R { try { @@ -118,13 +122,13 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto */ @Throws(NotSerializableException::class) fun deserialize(bytes: ByteSequence, clazz: Class): T = des { - val envelope = getEnvelope(bytes) + val envelope = getEnvelope(bytes, encodingWhitelist) clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)) } @Throws(NotSerializableException::class) fun deserializeAndReturnEnvelope(bytes: SerializedBytes, clazz: Class): ObjectAndEnvelope = des { - val envelope = getEnvelope(bytes) + val envelope = getEnvelope(bytes, encodingWhitelist) // Now pick out the obj and schema from the envelope. ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)), envelope) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt index 5b489c5d84..10fcc8966b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt @@ -29,7 +29,7 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra fun get(data: Data): Envelope { val describedType = data.`object` as DescribedType if (describedType.descriptor != DESCRIPTOR) { - throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") + throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}, should be $DESCRIPTOR.") } val list = describedType.described as List<*> diff --git a/settings.gradle b/settings.gradle index 335d58496f..113b5fbaa5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,7 @@ include 'experimental:behave' include 'experimental:sandbox' include 'experimental:quasar-hook' include 'experimental:kryo-hook' +include 'experimental:blobinspector' include 'test-common' include 'test-utils' include 'smoke-test-utils' From 0494e4503753165de395be4fe657cdbf32e8b076 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 3 May 2018 13:44:05 +0100 Subject: [PATCH 02/18] Remove debug logging left over from earlier AMQP development. (#2867) --- .../internal/protonwrapper/engine/ConnectionStateMachine.kt | 1 - .../nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt | 2 -- 2 files changed, 3 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt index 42575770e0..1ea1040557 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt @@ -428,7 +428,6 @@ internal class ConnectionStateMachine(serverMode: Boolean, } fun transportWriteMessage(msg: SendableMessageImpl) { - log.debug { "Queue application message write uuid: ${msg.applicationProperties["_AMQ_DUPL_ID"]} ${javax.xml.bind.DatatypeConverter.printHexBinary(msg.payload)}" } msg.buf = encodePayloadBytes(msg) val messageQueue = messageQueues.getOrPut(msg.topic, { LinkedList() }) messageQueue.offer(msg) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt index 06237626bb..ae83f53455 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt @@ -103,7 +103,6 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { try { - log.debug { "Received $msg" } if (msg is ByteBuf) { eventProcessor!!.transportProcessInput(msg) } @@ -116,7 +115,6 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { try { try { - log.debug { "Sent $msg" } when (msg) { // Transfers application packet into the AMQP engine. is SendableMessageImpl -> { From db22c5259db30496b930a9112b5055f9837b94a1 Mon Sep 17 00:00:00 2001 From: Roger Willis Date: Thu, 3 May 2018 15:39:52 +0100 Subject: [PATCH 03/18] Feature to exclude CorDapps from specific MockNodes in a MockNetwork. (#3028) * Feature to exclude CorDapps from specific MockNode's in a MockNetwork. * Fixed API stability issue. * * Added docs. * Changed approach for the new parameter to be additive as opposed to exclusive. * * Updated incorrect comments. --- docs/source/api-testing.rst | 34 +++++++++++++++++++ .../net/corda/testing/node/MockNetwork.kt | 24 ++++++++----- .../node/internal/InternalMockNetwork.kt | 16 ++++++--- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/docs/source/api-testing.rst b/docs/source/api-testing.rst index b2662c1acf..5169f7b3b5 100644 --- a/docs/source/api-testing.rst +++ b/docs/source/api-testing.rst @@ -165,6 +165,40 @@ Nodes are created on the ``MockNetwork`` using: } } +Nodes added using ``createPartyNode`` are provided a default set of node parameters. However, it is also possible to +provide different parameters to each node using the following methods on ``MockNetwork``: + +.. container:: codeset + + .. sourcecode:: kotlin + + /** + * Create a started node with the given parameters. + * + * @param legalName The node's legal name. + * @param forcedID A unique identifier for the node. + * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overridden to cause nodes to have stable or colliding identity/service keys. + * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. + * @param extraCordappPackages Extra CorDapp packages to add for this node. + */ + @JvmOverloads + fun createNode(legalName: CordaX500Name? = null, + forcedID: Int? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + configOverrides: (NodeConfiguration) -> Any? = {}, + extraCordappPackages: List = emptyList() + ): StartedMockNode + + /** Create a started node with the given parameters. **/ + fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode + +As you can see above, parameters can be added individually or encapsulated within a ``MockNodeParameters`` object. Of +particular interest are ``configOverrides`` which allow you to override any default config option specified within the +``NodeConfiguration`` object. Also, the ``extraCordappPackages`` parameter allows you to add extra CorDapps to a +specific node. This is useful when you wish for all nodes to load a common CorDapp but for a subset of nodes to load +CorDapps specific to their role in the network. + Running the network ^^^^^^^^^^^^^^^^^^^ diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt index 05c9ca5d30..16be35160f 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt @@ -33,17 +33,23 @@ import java.nio.file.Path * @property entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object. + * @property extraCordappPackages Extra CorDapp packages to include for this node. */ @Suppress("unused") -data class MockNodeParameters( +data class MockNodeParameters @JvmOverloads constructor( val forcedID: Int? = null, val legalName: CordaX500Name? = null, val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - val configOverrides: (NodeConfiguration) -> Any? = {}) { + val configOverrides: (NodeConfiguration) -> Any? = {}, + val extraCordappPackages: List = emptyList()) { fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID) fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName) fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot) fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides) + fun withExtraCordappPackages(extraCordappPackages: List): MockNodeParameters = copy(extraCordappPackages = extraCordappPackages) + fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters { + return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides) + } } /** @@ -248,14 +254,15 @@ open class MockNetwork( * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param version The mock node's platform, release, revision and vendor versions. + * @param extraCordappPackages Extra CorDapp packages to add for this node. */ @JvmOverloads fun createNode(legalName: CordaX500Name? = null, forcedID: Int? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedMockNode { - val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides) + configOverrides: (NodeConfiguration) -> Any? = {}, + extraCordappPackages: List = emptyList()): StartedMockNode { + val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages) return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) } @@ -270,14 +277,15 @@ open class MockNetwork( * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param version The mock node's platform, release, revision and vendor versions. + * @param extraCordappPackages Extra CorDapp packages to add for this node. */ @JvmOverloads fun createUnstartedNode(legalName: CordaX500Name? = null, forcedID: Int? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): UnstartedMockNode { - val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides) + configOverrides: (NodeConfiguration) -> Any? = {}, + extraCordappPackages: List = emptyList()): UnstartedMockNode { + val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages) return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 971da142a7..a465cb3b65 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -74,7 +74,8 @@ data class MockNodeArgs( val network: InternalMockNetwork, val id: Int, val entropyRoot: BigInteger, - val version: VersionInfo = MOCK_VERSION_INFO + val version: VersionInfo = MOCK_VERSION_INFO, + val extraCordappPackages: List = emptyList() ) data class InternalMockNodeParameters( @@ -82,12 +83,16 @@ data class InternalMockNodeParameters( val legalName: CordaX500Name? = null, val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}, - val version: VersionInfo = MOCK_VERSION_INFO) { + val version: VersionInfo = MOCK_VERSION_INFO, + val extraCordappPackages: List = emptyList()) { constructor(mockNodeParameters: MockNodeParameters) : this( mockNodeParameters.forcedID, mockNodeParameters.legalName, mockNodeParameters.entropyRoot, - mockNodeParameters.configOverrides) + mockNodeParameters.configOverrides, + MOCK_VERSION_INFO, + mockNodeParameters.extraCordappPackages + ) } open class InternalMockNetwork(private val cordappPackages: List, @@ -218,7 +223,8 @@ open class InternalMockNetwork(private val cordappPackages: List, args.config, TestClock(Clock.systemUTC()), args.version, - CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), + // Add the specified additional CorDapps. + CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages + args.extraCordappPackages), args.network.busyLatch ) { companion object { @@ -369,7 +375,7 @@ open class InternalMockNetwork(private val cordappPackages: List, doReturn(emptyList()).whenever(it).extraNetworkMapKeys parameters.configOverrides(it) } - val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version)) + val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, parameters.extraCordappPackages)) _nodes += node if (start) { node.start() From a61aa5d6457b8ccb5273ce5aef34f15f94e9f497 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 3 May 2018 16:09:03 +0100 Subject: [PATCH 04/18] CORDA-847 - Pass serialization context down serialization call stack (#3062) * CORDA-847 - Pass serialization context down serialization call stack Needed for later work on AMQP RPC where we need to set per-thread elements on a context. Could use some magic thread local but I'd rather it was explicit on the stack and thus easier to reason about. Additionally, now we're passing this around we can make better use of it in the future * Test Fix * Test fixes * REVIEW COMMENTS / CODE FORMAT * Fix build issues --- ...VerificationExceptionSerialisationTests.kt | 31 ++++++-- docs/source/changelog.rst | 3 + .../net/corda/blobinspector/InMemoryTests.kt | 14 ++-- .../engine/ConnectionStateMachine.kt | 2 +- .../serialization/CordaClassResolver.kt | 10 +-- .../serialization/DefaultWhitelist.kt | 2 +- .../serialization/SerializationFormat.kt | 2 +- .../serialization/SerializationScheme.kt | 8 +-- .../SerializeAsTokenContextImpl.kt | 3 +- .../serialization/UseCaseAwareness.kt | 3 +- .../amqp/AMQPDescriptorRegistry.kt | 2 +- .../amqp/AMQPPrimitiveSerializer.kt | 13 +++- .../amqp/AMQPSerializationScheme.kt | 20 +++--- .../serialization/amqp/AMQPSerializer.kt | 6 +- .../serialization/amqp/ArraySerializer.kt | 70 ++++++++++++------- .../amqp/CollectionSerializer.kt | 19 +++-- .../amqp/CorDappCustomSerializer.kt | 16 +++-- .../serialization/amqp/CustomSerializer.kt | 49 +++++++++---- .../amqp/DeserializationInput.kt | 51 +++++++++----- .../amqp/EnumEvolutionSerializer.kt | 12 +++- .../serialization/amqp/EnumSerializer.kt | 9 ++- .../serialization/amqp/EvolutionSerializer.kt | 50 +++++++------ .../serialization/amqp/MapSerializer.kt | 29 ++++---- .../serialization/amqp/ObjectSerializer.kt | 21 +++--- .../serialization/amqp/PropertySerializer.kt | 34 ++++++--- .../serialization/amqp/PropertySerializers.kt | 9 +-- .../serialization/amqp/SerializationHelper.kt | 17 +++-- .../serialization/amqp/SerializationOutput.kt | 31 ++++---- .../serialization/amqp/SerializerFactory.kt | 28 ++++---- .../serialization/amqp/SingletonSerializer.kt | 8 ++- .../serialization/amqp/TransformsSchema.kt | 16 ++--- .../amqp/custom/ClassSerializer.kt | 1 + .../amqp/custom/InputStreamSerializer.kt | 11 ++- .../amqp/custom/MonthDaySerializer.kt | 7 +- .../amqp/custom/OffsetDateTimeSerializer.kt | 4 +- .../amqp/custom/OffsetTimeSerializer.kt | 4 +- .../amqp/custom/PeriodSerializer.kt | 2 +- .../amqp/custom/PrivateKeySerializer.kt | 16 +++-- .../amqp/custom/PublicKeySerializer.kt | 13 ++-- .../amqp/custom/X509CRLSerializer.kt | 13 ++-- .../amqp/custom/X509CertificateSerializer.kt | 13 ++-- .../amqp/custom/YearMonthSerializer.kt | 2 +- .../amqp/custom/YearSerializer.kt | 2 +- .../amqp/custom/ZonedDateTimeSerializer.kt | 5 +- .../carpenter/AMQPSchemaExtensions.kt | 4 +- .../serialization/carpenter/ClassCarpenter.kt | 8 +-- .../serialization/carpenter/Exceptions.kt | 6 +- .../internal/serialization/kryo/Kryo.kt | 2 +- .../kryo/KryoSerializationScheme.kt | 14 ++-- .../serialization/kryo/KryoStreams.kt | 5 +- .../kryo/SerializeAsTokenSerializer.kt | 12 ++-- .../serialization/amqp/ErrorMessageTests.java | 4 +- .../serialization/amqp/JavaGenericsTest.java | 14 ++-- .../amqp/JavaNestedClassesTests.java | 31 ++++---- .../amqp/JavaNestedInheritenceTests.java | 7 +- .../amqp/JavaPrivatePropertyTests.java | 13 ++-- .../amqp/JavaSerialiseEnumTests.java | 3 +- .../amqp/JavaSerializationOutputTests.java | 9 ++- .../amqp/ListsSerializationJavaTest.java | 5 +- .../amqp/SetterConstructorTests.java | 51 +++++++++----- .../testutils/TestSerializationContext.java | 24 +++++++ .../serialization/amqp/AMQPTestUtils.kt | 32 --------- .../amqp/CorDappSerializerTests.kt | 6 ++ .../amqp/DeserializeAndReturnEnvelopeTests.kt | 7 ++ .../serialization/amqp/DeserializeMapTests.kt | 6 ++ .../DeserializeNeedingCarpentryOfEnumsTest.kt | 7 ++ ...erializeNeedingCarpentrySimpleTypesTest.kt | 6 ++ .../amqp/DeserializeNeedingCarpentryTests.kt | 5 ++ .../amqp/DeserializeSimpleTypesTests.kt | 4 ++ .../amqp/EnumEvolvabilityTests.kt | 5 ++ .../serialization/amqp/EnumEvolveTests.kt | 4 ++ .../internal/serialization/amqp/EnumTests.kt | 7 ++ .../serialization/amqp/ErrorMessagesTests.kt | 7 ++ .../serialization/amqp/EvolvabilityTests.kt | 4 ++ .../amqp/FingerPrinterTesting.kt | 2 + .../serialization/amqp/GenericsTests.kt | 7 +- .../amqp/OverridePKSerializerTest.kt | 8 ++- .../amqp/PrivatePropertyTests.kt | 4 ++ .../serialization/amqp/RoundTripTests.kt | 3 + .../amqp/SerializationOutputTests.kt | 13 ++-- .../amqp/SerializationPropertyOrdering.kt | 6 ++ .../amqp/SerializeAndReturnSchemaTest.kt | 3 + ...ticInitialisationOfSerializedObjectTest.kt | 4 ++ .../amqp/testutils/AMQPTestUtils.kt | 67 ++++++++++++++++++ .../testutils/TestSerializationContext.kt | 17 +++++ .../carpenter/CarpenterExceptionTests.kt | 4 ++ .../carpenter/ClassCarpenterTestUtils.kt | 2 +- ...berCompositeSchemaToClassCarpenterTests.kt | 1 + .../InheritanceSchemaToClassCarpenterTests.kt | 1 + ...berCompositeSchemaToClassCarpenterTests.kt | 1 + ...berCompositeSchemaToClassCarpenterTests.kt | 1 + 91 files changed, 763 insertions(+), 374 deletions(-) create mode 100644 node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.java delete mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/AMQPTestUtils.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.kt diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt index 721a78ec6f..418226cea0 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts import net.corda.core.crypto.SecureHash import net.corda.core.transactions.LedgerTransaction +import net.corda.nodeapi.internal.serialization.AMQP_RPC_CLIENT_CONTEXT import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput @@ -19,13 +20,17 @@ class TransactionVerificationExceptionSerialisationTests { ClassLoader.getSystemClassLoader() ) + private val context get() = AMQP_RPC_CLIENT_CONTEXT + private val txid = SecureHash.allOnesHash private val factory = defaultFactory() @Test fun contractConstraintRejectionTest() { val excp = TransactionVerificationException.ContractConstraintRejection(txid, "This is only a test") - val excp2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(excp)) + val excp2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(excp, context), + context) assertEquals(excp.message, excp2.message) assertEquals(excp.cause, excp2.cause) @@ -42,7 +47,9 @@ class TransactionVerificationExceptionSerialisationTests { val cause = Throwable("wibble") val exception = TransactionVerificationException.ContractRejection(txid, contract, cause) - val exception2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(exception)) + val exception2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exception, context), + context) assertEquals(exception.message, exception2.message) assertEquals(exception.cause?.message, exception2.cause?.message) @@ -52,7 +59,9 @@ class TransactionVerificationExceptionSerialisationTests { @Test fun missingAttachmentRejectionTest() { val exception = TransactionVerificationException.MissingAttachmentRejection(txid, "Some contract class") - val exception2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(exception)) + val exception2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exception, context), + context) assertEquals(exception.message, exception2.message) assertEquals(exception.cause?.message, exception2.cause?.message) @@ -62,7 +71,9 @@ class TransactionVerificationExceptionSerialisationTests { @Test fun conflictingAttachmentsRejectionTest() { val exception = TransactionVerificationException.ContractConstraintRejection(txid, "Some contract class") - val exception2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(exception)) + val exception2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exception, context), + context) assertEquals(exception.message, exception2.message) assertEquals(exception.cause?.message, exception2.cause?.message) @@ -73,7 +84,9 @@ class TransactionVerificationExceptionSerialisationTests { fun contractCreationErrorTest() { val cause = Throwable("wibble") val exception = TransactionVerificationException.ContractCreationError(txid, "Some contract class", cause) - val exception2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(exception)) + val exception2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exception, context), + context) assertEquals(exception.message, exception2.message) assertEquals(exception.cause?.message, exception2.cause?.message) @@ -84,7 +97,9 @@ class TransactionVerificationExceptionSerialisationTests { fun transactionMissingEncumbranceTest() { val exception = TransactionVerificationException.TransactionMissingEncumbranceException( txid, 12, TransactionVerificationException.Direction.INPUT) - val exception2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(exception)) + val exception2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exception, context), + context) assertEquals(exception.message, exception2.message) assertEquals(exception.cause?.message, exception2.cause?.message) @@ -99,7 +114,9 @@ class TransactionVerificationExceptionSerialisationTests { val factory = defaultFactory() factory.register(PublicKeySerializer) val exception = TransactionVerificationException.NotaryChangeInWrongTransactionType(txid, dummyBankA, dummyNotary) - val exception2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(exception)) + val exception2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exception, context), + context) assertEquals(exception.message, exception2.message) assertEquals(exception.cause?.message, exception2.cause?.message) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 3f7a0481bb..5d0f410e30 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,9 @@ release, see :doc:`upgrade-notes`. Unreleased ========== +* Refactor AMQP Serializer to pass context object down the serialization call hierarchy. Will allow per thread + extensions to be set and used by the RPC work (Observable Context Key) + * Refactor RPC Server Kryo observable serializer into it's own sub module * Refactor RPC Client Kryo observable serializer into it's own sub module diff --git a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt index d8451df92d..26313b1d3c 100644 --- a/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt +++ b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt @@ -4,6 +4,7 @@ import net.corda.core.serialization.SerializedBytes import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import org.junit.Test @@ -21,13 +22,13 @@ class InMemoryTests { @Test fun test1() { data class C (val a: Int, val b: Long, val c: String) - inspect (SerializationOutput(factory).serialize(C(100, 567L, "this is a test"))) + inspect (SerializationOutput(factory).serialize(C(100, 567L, "this is a test"), AMQP_P2P_CONTEXT)) } @Test fun test2() { data class C (val i: Int, val c: C?) - inspect (SerializationOutput(factory).serialize(C(1, C(2, C(3, C(4, null)))))) + inspect (SerializationOutput(factory).serialize(C(1, C(2, C(3, C(4, null)))), AMQP_P2P_CONTEXT)) } @Test @@ -37,7 +38,7 @@ class InMemoryTests { val a = IntArray(10) { i -> i } val c = C(a, arrayOf("aaa", "bbb", "ccc")) - inspect (SerializationOutput(factory).serialize(c)) + inspect (SerializationOutput(factory).serialize(c, AMQP_P2P_CONTEXT)) } @Test @@ -51,7 +52,7 @@ class InMemoryTests { Elem(1L, "First element"), Elem(2L, "Second element"), Elem(3L, "Third element") - )))) + )), AMQP_P2P_CONTEXT)) } @Test @@ -72,7 +73,7 @@ class InMemoryTests { Elem(5L, "Fifth element"), Elem(6L, "Sixth element") ) - )))) + )), AMQP_P2P_CONTEXT)) } @Test @@ -83,7 +84,8 @@ class InMemoryTests { C(mapOf( "a" to "a a a", "b" to "b b b", - "c" to "c c c")) + "c" to "c c c")), + AMQP_P2P_CONTEXT )) } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt index 1ea1040557..2b7f767c5d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt @@ -351,7 +351,7 @@ internal class ConnectionStateMachine(serverMode: Boolean, val connection = event.connection val channel = connection?.context as? Channel if (channel != null) { - val appProperties = HashMap(amqpMessage.applicationProperties.value) + val appProperties = HashMap(amqpMessage.applicationProperties.value as Map) appProperties["_AMQ_VALIDATED_USER"] = remoteLegalName val localAddress = channel.localAddress() as InetSocketAddress val remoteAddress = channel.remoteAddress() as InetSocketAddress diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt index c6458e53a4..fe94544a35 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt @@ -80,10 +80,10 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl val objectInstance = try { targetType.declaredFields.singleOrNull { it.name == "INSTANCE" && - it.type == type && - Modifier.isStatic(it.modifiers) && - Modifier.isFinal(it.modifiers) && - Modifier.isPublic(it.modifiers) + it.type == type && + Modifier.isStatic(it.modifiers) && + Modifier.isFinal(it.modifiers) && + Modifier.isPublic(it.modifiers) }?.let { it.isAccessible = true type.cast(it.get(null)!!) @@ -162,7 +162,7 @@ object AllWhitelist : ClassWhitelist { override fun hasListed(type: Class<*>): Boolean = true } -sealed class AbstractMutableClassWhitelist(private val whitelist: MutableSet, private val delegate: ClassWhitelist) : MutableClassWhitelist { +sealed class AbstractMutableClassWhitelist(private val whitelist: MutableSet, private val delegate: ClassWhitelist) : MutableClassWhitelist { override fun hasListed(type: Class<*>): Boolean { /** diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt index 101936ba82..e98cded106 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt @@ -66,5 +66,5 @@ object DefaultWhitelist : SerializationWhitelist { // Implementation of X509Certificate. X509CertImpl::class.java, CRLReason::class.java - ) + ) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt index fefdfb930f..84d6aef83b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt @@ -7,8 +7,8 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.serialization.OrdinalBits.OrdinalWriter import org.iq80.snappy.SnappyFramedInputStream import org.iq80.snappy.SnappyFramedOutputStream -import java.io.OutputStream import java.io.InputStream +import java.io.OutputStream import java.nio.ByteBuffer import java.util.zip.DeflaterOutputStream import java.util.zip.InflaterInputStream diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index fa67d13c09..d99227f800 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -39,13 +39,15 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe */ override fun withAttachmentsClassLoader(attachmentHashes: List): SerializationContext { properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this - val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContextImpl ?: return this // Some tests don't set one. + val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContextImpl + ?: return this // Some tests don't set one. try { return withClassLoader(cache.get(attachmentHashes) { val missing = ArrayList() val attachments = ArrayList() attachmentHashes.forEach { id -> - serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id } + serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } + ?: run { missing += id } } missing.isNotEmpty() && throw MissingAttachmentsException(missing) AttachmentsClassLoader(attachments, parent = deserializationClassLoader) @@ -143,8 +145,6 @@ open class SerializationFactoryImpl : SerializationFactory() { } - - interface SerializationScheme { fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean @Throws(NotSerializableException::class) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenContextImpl.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenContextImpl.kt index 6877239bd5..8451ac62b2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenContextImpl.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenContextImpl.kt @@ -52,5 +52,6 @@ class SerializeAsTokenContextImpl(override val serviceHub: ServiceHub, init: Ser } } - override fun getSingleton(className: String) = classNameToSingleton[className] ?: throw IllegalStateException("Unable to find tokenized instance of $className in context $this") + override fun getSingleton(className: String) = classNameToSingleton[className] + ?: throw IllegalStateException("Unable to find tokenized instance of $className in context $this") } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/UseCaseAwareness.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/UseCaseAwareness.kt index 7f1d8c17b4..6a828e8cc0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/UseCaseAwareness.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/UseCaseAwareness.kt @@ -5,7 +5,8 @@ import net.corda.core.serialization.SerializationFactory import java.util.* internal fun checkUseCase(allowedUseCases: EnumSet) { - val currentContext: SerializationContext = SerializationFactory.currentFactory?.currentContext ?: throw IllegalStateException("Current context is not set") + val currentContext: SerializationContext = SerializationFactory.currentFactory?.currentContext + ?: throw IllegalStateException("Current context is not set") if (!allowedUseCases.contains(currentContext.useCase)) { throw IllegalStateException("UseCase '${currentContext.useCase}' is not within '$allowedUseCases'") } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt index 3907a86fe8..0f6683202b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt @@ -10,7 +10,7 @@ import org.apache.qpid.proton.amqp.UnsignedLong * Repeated here for brevity: * 50530 - R3 - Mike Hearn - mike&r3.com */ -const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl(32 + 16) +const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl (32 + 16) /** * AMQP descriptor ID's for our custom types. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt index 27db491d33..f3ddd3947f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data @@ -18,7 +19,14 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer { override fun writeClassInfo(output: SerializationOutput) { } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject( + obj: Any, + data: Data, + type: Type, + output: SerializationOutput, + context: SerializationContext, + debugIndent: Int + ) { if (obj is ByteArray) { data.putObject(Binary(obj)) } else { @@ -29,5 +37,6 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer { override fun readObject( obj: Any, schemas: SerializationSchemas, - input: DeserializationInput): Any = (obj as? Binary)?.array ?: obj + input: DeserializationInput, + context: SerializationContext): Any = (obj as? Binary)?.array ?: obj } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index 141f9af528..ed1ab9c4ed 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -6,8 +6,8 @@ import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import net.corda.core.cordapp.Cordapp import net.corda.core.internal.objectOrNewInstance import net.corda.core.serialization.* -import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.utilities.ByteSequence +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.nodeapi.internal.serialization.DefaultWhitelist import net.corda.nodeapi.internal.serialization.MutableClassWhitelist import net.corda.nodeapi.internal.serialization.SerializationScheme @@ -31,12 +31,12 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) { open class SerializerFactoryFactory { open fun make(context: SerializationContext) = - SerializerFactory(context.whitelist, context.deserializationClassLoader) + SerializerFactory(context.whitelist, context.deserializationClassLoader) } abstract class AbstractAMQPSerializationScheme( val cordappLoader: List, - val sff : SerializerFactoryFactory = SerializerFactoryFactory() + val sff: SerializerFactoryFactory = SerializerFactoryFactory() ) : SerializationScheme { // TODO: This method of initialisation for the Whitelist and plugin serializers will have to change // when we have per-cordapp contexts and dynamic app reloading but for now it's the easiest way @@ -52,7 +52,7 @@ abstract class AbstractAMQPSerializationScheme( val scanSpec: String? = System.getProperty(SCAN_SPEC_PROP_NAME) - if(scanSpec == null) { + if (scanSpec == null) { emptyList() } else { FastClasspathScanner(scanSpec).addClassLoader(this::class.java.classLoader).scan() @@ -64,7 +64,7 @@ abstract class AbstractAMQPSerializationScheme( } } - private fun registerCustomSerializers(factory: SerializerFactory) { + private fun registerCustomSerializers(context: SerializationContext, factory: SerializerFactory) { with(factory) { register(publicKeySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer) @@ -121,8 +121,7 @@ abstract class AbstractAMQPSerializationScheme( protected abstract fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory - open protected val publicKeySerializer: CustomSerializer.Implements - = net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer + protected open val publicKeySerializer: CustomSerializer.Implements = net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer private fun getSerializerFactory(context: SerializationContext): SerializerFactory { return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { @@ -135,19 +134,20 @@ abstract class AbstractAMQPSerializationScheme( rpcServerSerializerFactory(context) else -> sff.make(context) }.also { - registerCustomSerializers(it) + registerCustomSerializers(context, it) } } } override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { val serializerFactory = getSerializerFactory(context) - return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz) + return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context) } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { val serializerFactory = getSerializerFactory(context) - return SerializationOutput(serializerFactory).serialize(obj) + + return SerializationOutput(serializerFactory).serialize(obj, context) } protected fun canDeserializeVersion(magic: CordaSerializationMagic) = magic == amqpMagic diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt index 9dfcd03cb7..e06412e4bb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.lang.reflect.Type @@ -30,10 +31,11 @@ interface AMQPSerializer { /** * Write the given object, with declared type, to the output. */ - fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int = 0) + fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int = 0) /** * Read the given object from the input. The envelope is provided in case the schema is required. */ - fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T + fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): T } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt index 872709a119..e16872809e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException @@ -32,7 +33,8 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory) } override val typeDescriptor by lazy { - Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") } + Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") + } internal val elementType: Type by lazy { type.componentType() } internal open val typeName by lazy { calcTypeName(type) } @@ -46,20 +48,24 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory) } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { // Write described data.withDescribed(typeNotation.descriptor) { withList { for (entry in obj as Array<*>) { - output.writeObjectOrNull(entry, this, elementType, debugIndent) + output.writeObjectOrNull(entry, this, elementType, context, debugIndent) } } } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): Any { if (obj is List<*>) { - return obj.map { input.readObjectOrNull(it, schemas, elementType) }.toArrayOfType(elementType) + return obj.map { input.readObjectOrNull(it, schemas, elementType, context) }.toArrayOfType(elementType) } else throw NotSerializableException("Expected a List but found $obj") } @@ -108,20 +114,24 @@ abstract class PrimArraySerializer(type: Type, factory: SerializerFactory) : Arr } } -class PrimIntArraySerializer(factory: SerializerFactory) : - PrimArraySerializer(IntArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { +class PrimIntArraySerializer(factory: SerializerFactory) : PrimArraySerializer(IntArray::class.java, factory) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { localWriteObject(data) { - (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) } } } } -class PrimCharArraySerializer(factory: SerializerFactory) : - PrimArraySerializer(CharArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { - localWriteObject(data) { (obj as CharArray).forEach { - output.writeObjectOrNull(it, data, elementType, debugIndent+1) } +class PrimCharArraySerializer(factory: SerializerFactory) : PrimArraySerializer(CharArray::class.java, factory) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { + localWriteObject(data) { + (obj as CharArray).forEach { + output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) + } } } @@ -135,47 +145,55 @@ class PrimCharArraySerializer(factory: SerializerFactory) : } } -class PrimBooleanArraySerializer(factory: SerializerFactory) : - PrimArraySerializer(BooleanArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { +class PrimBooleanArraySerializer(factory: SerializerFactory) : PrimArraySerializer(BooleanArray::class.java, factory) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { localWriteObject(data) { - (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) } } } } class PrimDoubleArraySerializer(factory: SerializerFactory) : PrimArraySerializer(DoubleArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { localWriteObject(data) { - (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) } } } } class PrimFloatArraySerializer(factory: SerializerFactory) : PrimArraySerializer(FloatArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int) { localWriteObject(data) { - (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) } } } } class PrimShortArraySerializer(factory: SerializerFactory) : PrimArraySerializer(ShortArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { localWriteObject(data) { - (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) } } } } class PrimLongArraySerializer(factory: SerializerFactory) : PrimArraySerializer(LongArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { localWriteObject(data) { - (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, context, debugIndent + 1) } } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt index 5a6ab42f8e..9c41c37fe3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.NonEmptySet import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data @@ -8,15 +9,14 @@ import java.io.NotSerializableException import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.util.* -import kotlin.collections.Collection import kotlin.collections.LinkedHashSet -import kotlin.collections.Set /** * Serialization / deserialization of predefined set of supported [Collection] types covering mostly [List]s and [Set]s. */ class CollectionSerializer(val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer { - override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) + override val type: Type = declaredType as? DeserializedParameterizedType + ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) override val typeDescriptor by lazy { Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") } @@ -50,7 +50,8 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali } private fun deriveParametrizedType(declaredType: Type, collectionClass: Class>): ParameterizedType = - (declaredType as? ParameterizedType) ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType)) + (declaredType as? ParameterizedType) + ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType)) private fun findMostSuitableCollectionType(actualClass: Class<*>): Class> = @@ -73,12 +74,13 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) { // Write described data.withDescribed(typeNotation.descriptor) { withList { for (entry in obj as Collection<*>) { - output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], debugIndent) + output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], context, debugIndent) } } } @@ -87,8 +89,11 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali override fun readObject( obj: Any, schemas: SerializationSchemas, - input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) { + input: DeserializationInput, + context: SerializationContext): Any = ifThrowsAppend({ declaredType.typeName }) { // TODO: Can we verify the entries in the list? - concreteBuilder((obj as List<*>).map { input.readObjectOrNull(it, schemas, declaredType.actualTypeArguments[0]) }) + concreteBuilder((obj as List<*>).map { + input.readObjectOrNull(it, schemas, declaredType.actualTypeArguments[0], context) + }) } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt index 161c18d4cc..bb40f19454 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationCustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import org.apache.qpid.proton.amqp.Symbol @@ -64,22 +65,25 @@ class CorDappCustomSerializer( override fun writeClassInfo(output: SerializationOutput) {} - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { val proxy = uncheckedCast, SerializationCustomSerializer>(serializer).toProxy(obj) data.withDescribed(descriptor) { data.withList { - proxySerializer.propertySerializers.serializationOrder.forEach { - it.getter.writeProperty(proxy, this, output) + proxySerializer.propertySerializers.serializationOrder.forEach { + it.getter.writeProperty(proxy, this, output, context) } } } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput) = - uncheckedCast, SerializationCustomSerializer>( - serializer).fromProxy(uncheckedCast(proxySerializer.readObject(obj, schemas, input)))!! + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ) = uncheckedCast, SerializationCustomSerializer>( + serializer).fromProxy(uncheckedCast(proxySerializer.readObject(obj, schemas, input, context)))!! override fun isSerializerFor(clazz: Class<*>) = clazz == type } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt index fd02997c49..631cfc1bf6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data @@ -40,13 +41,16 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { */ override val revealSubclassesInSchema: Boolean get() = false - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { data.withDescribed(descriptor) { - writeDescribedObject(uncheckedCast(obj), data, type, output) + writeDescribedObject(uncheckedCast(obj), data, type, output, context) } } - abstract fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) + abstract fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext) /** * This custom serializer represents a sort of symbolic link from a subclass to a super class, where the super @@ -77,12 +81,16 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { override val descriptor: Descriptor = Descriptor(typeDescriptor) - override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) { - superClassSerializer.writeDescribedObject(obj, data, type, output) + override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { + superClassSerializer.writeDescribedObject(obj, data, type, output, context) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T { - return superClassSerializer.readObject(obj, schemas, input) + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): T { + return superClassSerializer.readObject(obj, schemas, input, context) } } @@ -124,7 +132,12 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(proxyClass, factory) } override val schemaForDocumentation: Schema by lazy { - val typeNotations = mutableSetOf(CompositeType(nameForType(type), null, emptyList(), descriptor, (proxySerializer.typeNotation as CompositeType).fields)) + val typeNotations = mutableSetOf( + CompositeType( + nameForType(type), + null, + emptyList(), + descriptor, (proxySerializer.typeNotation as CompositeType).fields)) for (additional in additionalSerializers) { typeNotations.addAll(additional.schemaForDocumentation.types) } @@ -138,17 +151,21 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { protected abstract fun fromProxy(proxy: P): T - override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) { + override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { val proxy = toProxy(obj) data.withList { proxySerializer.propertySerializers.serializationOrder.forEach { - it.getter.writeProperty(proxy, this, output) + it.getter.writeProperty(proxy, this, output, context) } } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T { - val proxy: P = uncheckedCast(proxySerializer.readObject(obj, schemas, input)) + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): T { + val proxy: P = uncheckedCast(proxySerializer.readObject(obj, schemas, input, context)) return fromProxy(proxy) } } @@ -176,11 +193,15 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { SerializerFactory.primitiveTypeName(String::class.java)!!, descriptor, emptyList()))) - override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) { + override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { data.putString(unmaker(obj)) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): T { val proxy = obj as String return maker(proxy) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index fdfaf01d81..71c303439b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -4,6 +4,7 @@ import com.esotericsoftware.kryo.io.ByteBufferInputStream import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.getStackTraceAsString import net.corda.core.serialization.EncodingWhitelist +import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializedBytes import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding @@ -62,7 +63,8 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto @Throws(NotSerializableException::class) fun withDataBytes(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist, task: (ByteBuffer) -> T): T { // Check that the lead bytes match expected header - val amqpSequence = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.") + val amqpSequence = amqpMagic.consume(byteSequence) + ?: throw NotSerializableException("Serialization header does not match.") var stream: InputStream = ByteBufferInputStream(amqpSequence) try { while (true) { @@ -96,11 +98,9 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto fun getEnvelope(byteSequence: ByteSequence) = Companion.getEnvelope(byteSequence, encodingWhitelist) @Throws(NotSerializableException::class) - inline fun deserialize(bytes: SerializedBytes): T = deserialize(bytes, T::class.java) + inline fun deserialize(bytes: SerializedBytes, context: SerializationContext): T = + deserialize(bytes, T::class.java, context) - @Throws(NotSerializableException::class) - inline internal fun deserializeAndReturnEnvelope(bytes: SerializedBytes): ObjectAndEnvelope = - deserializeAndReturnEnvelope(bytes, T::class.java) @Throws(NotSerializableException::class) private fun des(generator: () -> R): R { @@ -121,23 +121,37 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto * be deserialized and a schema describing the types of the objects. */ @Throws(NotSerializableException::class) - fun deserialize(bytes: ByteSequence, clazz: Class): T = des { - val envelope = getEnvelope(bytes, encodingWhitelist) - clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)) - } + fun deserialize(bytes: ByteSequence, clazz: Class, context: SerializationContext): T = + des { + val envelope = getEnvelope(bytes, encodingWhitelist) + clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), + clazz, context)) + } @Throws(NotSerializableException::class) - fun deserializeAndReturnEnvelope(bytes: SerializedBytes, clazz: Class): ObjectAndEnvelope = des { + fun deserializeAndReturnEnvelope( + bytes: SerializedBytes, + clazz: Class, + context: SerializationContext + ): ObjectAndEnvelope = des { val envelope = getEnvelope(bytes, encodingWhitelist) // Now pick out the obj and schema from the envelope. - ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)), envelope) + ObjectAndEnvelope( + clazz.cast(readObjectOrNull( + envelope.obj, + SerializationSchemas(envelope.schema, envelope.transformsSchema), + clazz, + context)), + envelope) } - internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, offset: Int = 0): Any? { - return if (obj == null) null else readObject(obj, schema, type, offset) + internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, context: SerializationContext, + offset: Int = 0 + ): Any? { + return if (obj == null) null else readObject(obj, schema, type, context, offset) } - internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, debugIndent: Int = 0): Any = + internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, context: SerializationContext, debugIndent: Int = 0): Any = if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. val objectIndex = (obj.described as UnsignedInteger).toInt() @@ -158,19 +172,20 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto // Look up serializer in factory by descriptor val serializer = serializerFactory.get(obj.descriptor, schemas) if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { - !isSubClassOf(type) && !materiallyEquivalentTo(type) - }) { + !isSubClassOf(type) && !materiallyEquivalentTo(type) + }) { throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " + "expected to be of type $type but was ${serializer.type}") } - serializer.readObject(obj.described, schemas, this) + serializer.readObject(obj.described, schemas, this, context) } is Binary -> obj.array else -> obj // this will be the case for primitive types like [boolean] et al. } // Store the reference in case we need it later on. - // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content + // Skip for primitive types as they are too small and overhead of referencing them will be much higher + // than their content if (suitableForObjectReference(objectRead.javaClass)) { objectHistory.add(objectRead) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt index a6d9ebb055..ee49ae6e69 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException @@ -65,7 +66,8 @@ class EnumEvolutionSerializer( new: AMQPSerializer, factory: SerializerFactory, schemas: SerializationSchemas): AMQPSerializer { - val wireTransforms = schemas.transforms.types[old.name] ?: EnumMap>(TransformTypes::class.java) + val wireTransforms = schemas.transforms.types[old.name] + ?: EnumMap>(TransformTypes::class.java) val localTransforms = TransformsSchema.get(old.name, factory) // remember, the longer the list the newer we're assuming the transform set it as we assume @@ -117,7 +119,9 @@ class EnumEvolutionSerializer( } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): Any { val enumName = (obj as List<*>)[0] as String if (enumName !in conversions) { @@ -131,7 +135,9 @@ class EnumEvolutionSerializer( throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt index c3d9fdd732..8cc39e6079 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException @@ -28,7 +29,9 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria output.writeTypeNotations(typeNotation) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): Any { val enumName = (obj as List<*>)[0] as String val enumOrd = obj[1] as Int val fromOrd = type.asClass()!!.enumConstants[enumOrd] as Enum<*>? @@ -40,7 +43,9 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria return fromOrd } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't") data.withDescribed(typeNotation.descriptor) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index b2dfa16f53..a83e4744cf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -1,10 +1,11 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.DeprecatedConstructorForDeserialization +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.serialization.carpenter.getTypeAsClass import org.apache.qpid.proton.codec.Data -import java.lang.reflect.Type import java.io.NotSerializableException +import java.lang.reflect.Type import kotlin.reflect.KFunction import kotlin.reflect.full.findAnnotation import kotlin.reflect.jvm.javaType @@ -39,12 +40,13 @@ abstract class EvolutionSerializer( * @param property object to read the actual property value */ data class OldParam(var resultsIndex: Int, val property: PropertySerializer) { - fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, new: Array) = - property.readProperty(obj, schemas, input).apply { - if(resultsIndex >= 0) { - new[resultsIndex] = this - } - } + fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, + new: Array, context: SerializationContext + ) = property.readProperty(obj, schemas, input, context).apply { + if (resultsIndex >= 0) { + new[resultsIndex] = this + } + } } companion object { @@ -96,7 +98,7 @@ abstract class EvolutionSerializer( "New parameter ${it.value.name} is mandatory, should be nullable for evolution to worK") } } - return EvolutionSerializerViaConstructor (new.type, factory, readersAsSerialized, constructor, constructorArgs) + return EvolutionSerializerViaConstructor(new.type, factory, readersAsSerialized, constructor, constructorArgs) } private fun makeWithSetters( @@ -108,7 +110,7 @@ abstract class EvolutionSerializer( val setters = propertiesForSerializationFromSetters(classProperties, new.type, factory).associateBy({ it.getter.name }, { it }) - return EvolutionSerializerViaSetters (new.type, factory, readersAsSerialized, constructor, setters) + return EvolutionSerializerViaSetters(new.type, factory, readersAsSerialized, constructor, setters) } /** @@ -143,14 +145,15 @@ abstract class EvolutionSerializer( return if (classProperties.isNotEmpty() && constructor.parameters.isEmpty()) { makeWithSetters(new, factory, constructor, readersAsSerialized, classProperties) - } - else { + } else { makeWithConstructor(new, factory, constructor, readersAsSerialized) } } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } } @@ -160,7 +163,7 @@ class EvolutionSerializerViaConstructor( factory: SerializerFactory, oldReaders: Map, kotlinConstructor: KFunction?, - private val constructorArgs: Array) : EvolutionSerializer (clazz, factory, oldReaders, kotlinConstructor) { + private val constructorArgs: Array) : EvolutionSerializer(clazz, factory, oldReaders, kotlinConstructor) { /** * Unlike a normal [readObject] call where we simply apply the parameter deserialisers * to the object list of values we need to map that list, which is ordered per the @@ -170,15 +173,16 @@ class EvolutionSerializerViaConstructor( * * TODO: Object references */ - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): Any { if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj") // *must* read all the parameters in the order they were serialized - oldReaders.values.zip(obj).map { it.first.readProperty(it.second, schemas, input, constructorArgs) } + oldReaders.values.zip(obj).map { it.first.readProperty(it.second, schemas, input, constructorArgs, context) } - return javaConstructor?.newInstance(*(constructorArgs)) ?: - throw NotSerializableException( - "Attempt to deserialize an interface: $clazz. Serialized form is invalid.") + return javaConstructor?.newInstance(*(constructorArgs)) ?: throw NotSerializableException( + "Attempt to deserialize an interface: $clazz. Serialized form is invalid.") } } @@ -191,18 +195,20 @@ class EvolutionSerializerViaSetters( factory: SerializerFactory, oldReaders: Map, kotlinConstructor: KFunction?, - private val setters: Map) : EvolutionSerializer (clazz, factory, oldReaders, kotlinConstructor) { + private val setters: Map) : EvolutionSerializer(clazz, factory, oldReaders, kotlinConstructor) { - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): Any { if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj") - val instance : Any = javaConstructor?.newInstance() ?: throw NotSerializableException ( + val instance: Any = javaConstructor?.newInstance() ?: throw NotSerializableException( "Failed to instantiate instance of object $clazz") // *must* read all the parameters in the order they were serialized oldReaders.values.zip(obj).forEach { // if that property still exists on the new object then set it - it.first.property.readProperty(it.second, schemas, input).apply { + it.first.property.readProperty(it.second, schemas, input, context).apply { setters[it.first.property.name]?.set(instance, this) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt index 31def96b4f..d71aec7085 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException @@ -8,9 +9,6 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.util.* import kotlin.collections.LinkedHashMap -import kotlin.collections.Map -import kotlin.collections.iterator -import kotlin.collections.map private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *> @@ -18,8 +16,8 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *> * Serialization / deserialization of certain supported [Map] types. */ class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer { - override val type: Type = (declaredType as? DeserializedParameterizedType) ?: - DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType), factory.classloader) + override val type: Type = (declaredType as? DeserializedParameterizedType) + ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType), factory.classloader) override val typeDescriptor: Symbol = Symbol.valueOf( "$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") @@ -57,7 +55,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial } private fun deriveParametrizedType(declaredType: Type, collectionClass: Class>): ParameterizedType = - (declaredType as? ParameterizedType) ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType, SerializerFactory.AnyType)) + (declaredType as? ParameterizedType) + ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType, SerializerFactory.AnyType)) private fun findMostSuitableMapType(actualClass: Class<*>): Class> = @@ -80,6 +79,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) { obj.javaClass.checkSupportedMapType() // Write described @@ -88,22 +88,25 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial data.putMap() data.enter() for ((key, value) in obj as Map<*, *>) { - output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], debugIndent) - output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], debugIndent) + output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], context, debugIndent) + output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], context, debugIndent) } data.exit() // exit map } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): Any = ifThrowsAppend({ declaredType.typeName }) { // TODO: General generics question. Do we need to validate that entries in Maps and Collections match the generic type? Is it a security hole? - val entries: Iterable> = (obj as Map<*, *>).map { readEntry(schemas, input, it) } + val entries: Iterable> = (obj as Map<*, *>).map { readEntry(schemas, input, it, context) } concreteBuilder(entries.toMap()) } - private fun readEntry(schemas: SerializationSchemas, input: DeserializationInput, entry: Map.Entry) = - input.readObjectOrNull(entry.key, schemas, declaredType.actualTypeArguments[0]) to - input.readObjectOrNull(entry.value, schemas, declaredType.actualTypeArguments[1]) + private fun readEntry(schemas: SerializationSchemas, input: DeserializationInput, entry: Map.Entry, + context: SerializationContext + ) = input.readObjectOrNull(entry.key, schemas, declaredType.actualTypeArguments[0], context) to + input.readObjectOrNull(entry.value, schemas, declaredType.actualTypeArguments[1], context) // Cannot use * as a bound for EnumMap and EnumSet since * is not an enum. So, we use a sample enum instead. // We don't actually care about the type, we just need to make the compiler happier. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index 2403d825c8..f9158b74c7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType @@ -57,6 +58,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int) = ifThrowsAppend({ clazz.typeName } ) { if (propertySerializers.size != javaConstructor?.parameterCount && @@ -72,7 +74,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS // Write list withList { propertySerializers.serializationOrder.forEach { property -> - property.getter.writeProperty(obj, this, output, debugIndent + 1) + property.getter.writeProperty(obj, this, output, context, debugIndent + 1) } } } @@ -81,16 +83,17 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS override fun readObject( obj: Any, schemas: SerializationSchemas, - input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) { + input: DeserializationInput, + context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) { if (obj is List<*>) { if (obj.size > propertySerializers.size) { throw NotSerializableException("Too many properties in described type $typeName") } return if (propertySerializers.byConstructor) { - readObjectBuildViaConstructor(obj, schemas, input) + readObjectBuildViaConstructor(obj, schemas, input, context) } else { - readObjectBuildViaSetters(obj, schemas, input) + readObjectBuildViaSetters(obj, schemas, input, context) } } else { throw NotSerializableException("Body of described type is unexpected $obj") @@ -100,12 +103,13 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS private fun readObjectBuildViaConstructor( obj: List<*>, schemas: SerializationSchemas, - input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) { + input: DeserializationInput, + context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) { logger.trace { "Calling construction based construction for ${clazz.typeName}" } return construct(propertySerializers.serializationOrder .zip(obj) - .map { Pair(it.first.initialPosition, it.first.getter.readProperty(it.second, schemas, input)) } + .map { Pair(it.first.initialPosition, it.first.getter.readProperty(it.second, schemas, input, context)) } .sortedWith(compareBy({ it.first })) .map { it.second }) } @@ -113,7 +117,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS private fun readObjectBuildViaSetters( obj: List<*>, schemas: SerializationSchemas, - input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) { + input: DeserializationInput, + context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) { logger.trace { "Calling setter based construction for ${clazz.typeName}" } val instance: Any = javaConstructor?.newInstance() ?: throw NotSerializableException( @@ -123,7 +128,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS // do it in doesn't matter val propertiesFromBlob = obj .zip(propertySerializers.serializationOrder) - .map { it.second.getter.readProperty(it.first, schemas, input) } + .map { it.second.getter.readProperty(it.first, schemas, input, context) } // one by one take a property and invoke the setter on the class propertySerializers.serializationOrder.zip(propertiesFromBlob).forEach { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt index f9fa3d8f83..f97955a8ca 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.codec.Data import java.lang.reflect.Type @@ -9,8 +10,8 @@ import java.lang.reflect.Type */ sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) { abstract fun writeClassInfo(output: SerializationOutput) - abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int = 0) - abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? + abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, context: SerializationContext, debugIndent: Int = 0) + abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any? val type: String = generateType() val requires: List = generateRequires() @@ -76,12 +77,15 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe override fun readProperty( obj: Any?, schemas: SerializationSchemas, - input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) { - input.readObjectOrNull(obj, schemas, resolvedType) + input: DeserializationInput, + context: SerializationContext): Any? = ifThrowsAppend({ nameForDebug }) { + input.readObjectOrNull(obj, schemas, resolvedType, context) } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) = ifThrowsAppend({ nameForDebug }) { - output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, debugIndent) + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, + context: SerializationContext, debugIndent: Int) = ifThrowsAppend({ nameForDebug } + ) { + output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, context, debugIndent) } private val nameForDebug = "$name(${resolvedType.typeName})" @@ -96,11 +100,15 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) { override fun writeClassInfo(output: SerializationOutput) {} - override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? { + override fun readProperty(obj: Any?, schemas: SerializationSchemas, + input: DeserializationInput, context: SerializationContext + ): Any? { return if (obj is Binary) obj.array else obj } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) { + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { val value = propertyReader.read(obj) if (value is ByteArray) { data.putObject(Binary(value)) @@ -112,18 +120,22 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe /** * A property serializer for the AMQP char type, needed as a specialisation as the underlying - * value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit + * value of the character is stored in numeric UTF-16 form and on deserialization requires explicit * casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs */ class AMQPCharPropertySerializer(name: String, readMethod: PropertyReader) : PropertySerializer(name, readMethod, Character::class.java) { override fun writeClassInfo(output: SerializationOutput) {} - override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? { + override fun readProperty(obj: Any?, schemas: SerializationSchemas, + input: DeserializationInput, context: SerializationContext + ): Any? { return if (obj == null) null else (obj as Short).toChar() } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) { + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { val input = propertyReader.read(obj) if (input != null) data.putShort((input as Char).toShort()) else data.putNull() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt index 4687c1173b..03f99f35e7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt @@ -2,12 +2,12 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.utilities.loggerFor import java.io.NotSerializableException +import java.lang.reflect.Field import java.lang.reflect.Method import java.lang.reflect.Type import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.kotlinProperty -import java.lang.reflect.Field abstract class PropertyReader { abstract fun read(obj: Any?): Any? @@ -141,6 +141,7 @@ class PropertyAccessorGetterSetter( */ setter.isAccessible = true } + /** * Invokes the setter on the underlying object passing in the serialized value. */ @@ -162,7 +163,7 @@ class PropertyAccessorConstructor( * calls to the explicit setter should be an error. */ override fun set(instance: Any, obj: Any?) { - NotSerializableException ("Attempting to access a setter on an object being instantiated " + + NotSerializableException("Attempting to access a setter on an object being instantiated " + "via its constructor.") } } @@ -187,7 +188,7 @@ abstract class PropertySerializers( is PropertyAccessorGetterSetter -> PropertySerializersSetter(serializationOrder) null -> PropertySerializersNoProperties() else -> { - throw NotSerializableException ("Unknown Property Accessor type, cannot create set") + throw NotSerializableException("Unknown Property Accessor type, cannot create set") } } } @@ -196,7 +197,7 @@ abstract class PropertySerializers( abstract val byConstructor: Boolean } -class PropertySerializersNoProperties : PropertySerializers (emptyList()) { +class PropertySerializersNoProperties : PropertySerializers(emptyList()) { override val byConstructor get() = true } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index e589716672..fe4af5d4bf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -95,7 +95,7 @@ data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter constructor() : this(null, null, null, null) - fun preferredGetter() : Method? = getter ?: iser + fun preferredGetter(): Method? = getter ?: iser } object PropertyDescriptorsRegex { @@ -163,8 +163,7 @@ fun Class.propertyDescriptors(): Map { // fails the getter doesn't refer to a property directly, but may refer to a constructor // parameter that shadows a property val properties = - classProperties[groups[2]!!.value] ?: - classProperties[groups[2]!!.value.decapitalize()] ?: + classProperties[groups[2]!!.value] ?: classProperties[groups[2]!!.value.decapitalize()] ?: // take into account those constructor properties that don't directly map to a named // property which are, by default, already added to the map classProperties.computeIfAbsent(groups[2]!!.value) { PropertyDescriptor() } @@ -245,9 +244,9 @@ internal fun propertiesForSerializationFromConstructor( // We will already have disambiguated getA for property A or a but we still need to cope // with the case we don't know the case of A when the parameter doesn't match a property // but has a getter - val matchingProperty = classProperties[name] ?: classProperties[name.capitalize()] ?: - throw NotSerializableException( - "Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"") + val matchingProperty = classProperties[name] ?: classProperties[name.capitalize()] + ?: throw NotSerializableException( + "Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"") // If the property has a getter we'll use that to retrieve it's value from the instance, if it doesn't // *for *know* we switch to a reflection based method @@ -267,8 +266,8 @@ internal fun propertiesForSerializationFromConstructor( Pair(PublicPropertyReader(getter), returnType) } else { - val field = classProperties[name]!!.field ?: - throw NotSerializableException("No property matching constructor parameter named - \"$name\" - " + + val field = classProperties[name]!!.field + ?: throw NotSerializableException("No property matching constructor parameter named - \"$name\" - " + "of \"$clazz\". If using Java, check that you have the -parameters option specified " + "in the Java compiler. Alternately, provide a proxy serializer " + "(SerializationCustomSerializer) if recompiling isn't an option") @@ -315,7 +314,7 @@ fun propertiesForSerializationFromSetters( } // Make sure the getter returns the same type (within inheritance bounds) the setter accepts. - if (!(TypeToken.of (getter.genericReturnType).isSupertypeOf(setterType))) { + if (!(TypeToken.of(getter.genericReturnType).isSupertypeOf(setterType))) { throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + "takes parameter of type $setterType yet the defined getter returns a value of type " + "${getter.returnType} [${getter.genericReturnType}]") diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt index 1dcf750ef5..3290c063ba 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationEncoding import net.corda.core.serialization.SerializedBytes import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding @@ -23,7 +24,10 @@ data class BytesAndSchemas( * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * instances and threads. */ -open class SerializationOutput @JvmOverloads constructor(internal val serializerFactory: SerializerFactory, private val encoding: SerializationEncoding? = null) { +open class SerializationOutput @JvmOverloads constructor( + internal val serializerFactory: SerializerFactory, + private val encoding: SerializationEncoding? = null +) { private val objectHistory: MutableMap = IdentityHashMap() private val serializerHistory: MutableSet> = LinkedHashSet() internal val schemaHistory: MutableSet = LinkedHashSet() @@ -34,19 +38,18 @@ open class SerializationOutput @JvmOverloads constructor(internal val serializer * of AMQP serialization constructed the serialized form. */ @Throws(NotSerializableException::class) - fun serialize(obj: T): SerializedBytes { + fun serialize(obj: T, context: SerializationContext): SerializedBytes { try { - return _serialize(obj) + return _serialize(obj, context) } finally { andFinally() } } - @Throws(NotSerializableException::class) - fun serializeAndReturnSchema(obj: T): BytesAndSchemas { + fun serializeAndReturnSchema(obj: T, context: SerializationContext): BytesAndSchemas { try { - val blob = _serialize(obj) + val blob = _serialize(obj, context) val schema = Schema(schemaHistory.toList()) return BytesAndSchemas(blob, schema, TransformsSchema.build(schema, serializerFactory)) } finally { @@ -60,11 +63,11 @@ open class SerializationOutput @JvmOverloads constructor(internal val serializer schemaHistory.clear() } - internal fun _serialize(obj: T): SerializedBytes { + internal fun _serialize(obj: T, context: SerializationContext): SerializedBytes { val data = Data.Factory.create() data.withDescribed(Envelope.DESCRIPTOR_OBJECT) { withList { - writeObject(obj, this) + writeObject(obj, this, context) val schema = Schema(schemaHistory.toList()) writeSchema(schema, this) writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this) @@ -87,8 +90,8 @@ open class SerializationOutput @JvmOverloads constructor(internal val serializer }) } - internal fun writeObject(obj: Any, data: Data) { - writeObject(obj, data, obj.javaClass) + internal fun writeObject(obj: Any, data: Data, context: SerializationContext) { + writeObject(obj, data, obj.javaClass, context) } open fun writeSchema(schema: Schema, data: Data) { @@ -99,15 +102,15 @@ open class SerializationOutput @JvmOverloads constructor(internal val serializer data.putObject(transformsSchema) } - internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, debugIndent: Int) { + internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, context: SerializationContext, debugIndent: Int) { if (obj == null) { data.putNull() } else { - writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, debugIndent) + writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, context, debugIndent) } } - internal fun writeObject(obj: Any, data: Data, type: Type, debugIndent: Int = 0) { + internal fun writeObject(obj: Any, data: Data, type: Type, context: SerializationContext, debugIndent: Int = 0) { val serializer = serializerFactory.get(obj.javaClass, type) if (serializer !in serializerHistory) { serializerHistory.add(serializer) @@ -116,7 +119,7 @@ open class SerializationOutput @JvmOverloads constructor(internal val serializer val retrievedRefCount = objectHistory[obj] if (retrievedRefCount == null) { - serializer.writeObject(obj, data, type, this, debugIndent) + serializer.writeObject(obj, data, type, this, context, debugIndent) // Important to do it after serialization such that dependent object will have preceding reference numbers // assigned to them first as they will be first read from the stream on receiving end. // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index b0f3d4746b..abf8034115 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -5,9 +5,11 @@ import com.google.common.reflect.TypeResolver import net.corda.core.internal.getStackTraceAsString import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.ClassWhitelist -import net.corda.core.utilities.contextLogger import net.corda.core.utilities.loggerFor -import net.corda.nodeapi.internal.serialization.carpenter.* +import net.corda.nodeapi.internal.serialization.carpenter.CarpenterMetaSchema +import net.corda.nodeapi.internal.serialization.carpenter.ClassCarpenter +import net.corda.nodeapi.internal.serialization.carpenter.MetaCarpenter +import net.corda.nodeapi.internal.serialization.carpenter.MetaCarpenterException import org.apache.qpid.proton.amqp.* import java.io.NotSerializableException import java.lang.reflect.* @@ -59,8 +61,7 @@ open class SerializerFactory( get() = classCarpenter.classloader private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer, - schemas: SerializationSchemas) - = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) + schemas: SerializationSchemas) = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) fun getSerializersByDescriptor() = serializersByDescriptor @@ -99,7 +100,8 @@ open class SerializerFactory( makeMapSerializer(declaredTypeAmended) } } - Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> serializersByType.computeIfAbsent(actualClass ?: declaredClass) { + Enum::class.java.isAssignableFrom(actualClass + ?: declaredClass) -> serializersByType.computeIfAbsent(actualClass ?: declaredClass) { whitelist.requireWhitelisted(actualType) EnumSerializer(actualType, actualClass ?: declaredClass, this) } @@ -244,8 +246,8 @@ open class SerializerFactory( } catch (e: MetaCarpenterException) { // preserve the actual message locally loggerFor().apply { - error ("${e.message} [hint: enable trace debugging for the stack trace]") - trace (e.getStackTraceAsString()) + error("${e.message} [hint: enable trace debugging for the stack trace]") + trace(e.getStackTraceAsString()) } // prevent carpenter exceptions escaping into the world, convert things into a nice @@ -283,12 +285,12 @@ open class SerializerFactory( } else { val singleton = clazz.objectInstance() if (singleton != null) { - whitelist.requireWhitelisted(clazz) - SingletonSerializer(clazz, singleton, this) - } else { - whitelist.requireWhitelisted(type) - ObjectSerializer(type, this) - } + whitelist.requireWhitelisted(clazz) + SingletonSerializer(clazz, singleton, this) + } else { + whitelist.requireWhitelisted(type) + ObjectSerializer(type, this) + } } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt index b54bb94393..e4d5869f06 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.lang.reflect.Type @@ -23,13 +24,16 @@ class SingletonSerializer(override val type: Class<*>, val singleton: Any, facto output.writeTypeNotations(typeNotation) } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext, debugIndent: Int + ) { data.withDescribed(typeNotation.descriptor) { data.putBoolean(false) } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext + ): Any { return singleton } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt index 50c38be9f5..829aac7b58 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt @@ -214,8 +214,8 @@ data class TransformsSchema(val types: Map ?: - throw NotSerializableException("Transform schema must be encoded as a map") + val map = describedType.described as? Map<*, *> + ?: throw NotSerializableException("Transform schema must be encoded as a map") map.forEach { type -> - val fingerprint = type.key as? String ?: - throw NotSerializableException("Fingerprint must be encoded as a string") + val fingerprint = type.key as? String + ?: throw NotSerializableException("Fingerprint must be encoded as a string") rtn[fingerprint] = EnumMap>(TransformTypes::class.java) @@ -288,8 +288,8 @@ data class TransformsSchema(val types: Map).forEach { - rtn[fingerprint]!![TransformTypes.newInstance(transformType)]?.add(Transform.newInstance(it)) ?: - throw NotSerializableException("De-serialization error with transform for class " + rtn[fingerprint]!![TransformTypes.newInstance(transformType)]?.add(Transform.newInstance(it)) + ?: throw NotSerializableException("De-serialization error with transform for class " + "${type.key} ${transform.name}") } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ClassSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ClassSerializer.kt index 7c399d5a68..99dd1b9ff6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ClassSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ClassSerializer.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer.ClassProxy /** * A serializer for [Class] that uses [ClassProxy] proxy object to write out diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt index ff2439bab9..faa4f5eebf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp.custom +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.serialization.amqp.* import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.codec.Data @@ -15,7 +16,9 @@ object InputStreamSerializer : CustomSerializer.Implements(InputStr override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - override fun writeDescribedObject(obj: InputStream, data: Data, type: Type, output: SerializationOutput) { + override fun writeDescribedObject(obj: InputStream, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { val startingSize = maxOf(4096, obj.available() + 1) var buffer = ByteArray(startingSize) var pos = 0 @@ -34,8 +37,10 @@ object InputStreamSerializer : CustomSerializer.Implements(InputStr } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): InputStream { - val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): InputStream { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray return bits.inputStream() } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/MonthDaySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/MonthDaySerializer.kt index e658ccf9a6..ba267f468c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/MonthDaySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/MonthDaySerializer.kt @@ -2,12 +2,15 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.MonthDay /** * A serializer for [MonthDay] that uses a proxy object to write out the integer form. */ -class MonthDaySerializer(factory: SerializerFactory) : CustomSerializer.Proxy(MonthDay::class.java, MonthDayProxy::class.java, factory) { +class MonthDaySerializer(factory: SerializerFactory) + : CustomSerializer.Proxy( + MonthDay::class.java, MonthDayProxy::class.java, factory +) { override fun toProxy(obj: MonthDay): MonthDayProxy = MonthDayProxy(obj.monthValue.toByte(), obj.dayOfMonth.toByte()) override fun fromProxy(proxy: MonthDayProxy): MonthDay = MonthDay.of(proxy.month.toInt(), proxy.day.toInt()) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetDateTimeSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetDateTimeSerializer.kt index 6000cb68cc..e0faf66299 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetDateTimeSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetDateTimeSerializer.kt @@ -2,7 +2,9 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset /** * A serializer for [OffsetDateTime] that uses a proxy object to write out the date and zone offset. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetTimeSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetTimeSerializer.kt index ec5d25e9a1..7f155c090b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetTimeSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OffsetTimeSerializer.kt @@ -2,7 +2,9 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.LocalTime +import java.time.OffsetTime +import java.time.ZoneOffset /** * A serializer for [OffsetTime] that uses a proxy object to write out the time and zone offset. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PeriodSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PeriodSerializer.kt index c850489d0c..3eca9063c6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PeriodSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PeriodSerializer.kt @@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.Period /** * A serializer for [Period] that uses a proxy object to write out the integer form. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PrivateKeySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PrivateKeySerializer.kt index 9609c926fe..e1310745bc 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PrivateKeySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PrivateKeySerializer.kt @@ -1,7 +1,9 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.core.crypto.Crypto -import net.corda.core.serialization.SerializationContext.UseCase.* +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint +import net.corda.core.serialization.SerializationContext.UseCase.Storage import net.corda.nodeapi.internal.serialization.amqp.* import net.corda.nodeapi.internal.serialization.checkUseCase import org.apache.qpid.proton.codec.Data @@ -15,13 +17,17 @@ object PrivateKeySerializer : CustomSerializer.Implements(PrivateKey override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - override fun writeDescribedObject(obj: PrivateKey, data: Data, type: Type, output: SerializationOutput) { + override fun writeDescribedObject(obj: PrivateKey, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { checkUseCase(allowedUseCases) - output.writeObject(obj.encoded, data, clazz) + output.writeObject(obj.encoded, data, clazz, context) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): PrivateKey { - val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): PrivateKey { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray return Crypto.decodePrivateKey(bits) } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PublicKeySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PublicKeySerializer.kt index 13faad17a6..3a9ac67fef 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PublicKeySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PublicKeySerializer.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.core.crypto.Crypto +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.serialization.amqp.* import org.apache.qpid.proton.codec.Data import java.lang.reflect.Type @@ -12,13 +13,17 @@ import java.security.PublicKey object PublicKeySerializer : CustomSerializer.Implements(PublicKey::class.java) { override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput) { + override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { // TODO: Instead of encoding to the default X509 format, we could have a custom per key type (space-efficient) serialiser. - output.writeObject(obj.encoded, data, clazz) + output.writeObject(obj.encoded, data, clazz, context) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): PublicKey { - val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): PublicKey { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray return Crypto.decodePublicKey(bits) } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CRLSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CRLSerializer.kt index 2f98bddb74..c0cf66a631 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CRLSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CRLSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp.custom +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.serialization.amqp.* import org.apache.qpid.proton.codec.Data @@ -16,12 +17,16 @@ object X509CRLSerializer : CustomSerializer.Implements(X509CRL::class.j emptyList() ))) - override fun writeDescribedObject(obj: X509CRL, data: Data, type: Type, output: SerializationOutput) { - output.writeObject(obj.encoded, data, clazz) + override fun writeDescribedObject(obj: X509CRL, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { + output.writeObject(obj.encoded, data, clazz, context) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): X509CRL { - val bytes = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): X509CRL { + val bytes = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray return X509CertificateFactory().delegate.generateCRL(bytes.inputStream()) as X509CRL } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt index dbbd6c6598..353f3acacf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp.custom +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.serialization.amqp.* import org.apache.qpid.proton.codec.Data @@ -16,12 +17,16 @@ object X509CertificateSerializer : CustomSerializer.Implements( emptyList() ))) - override fun writeDescribedObject(obj: X509Certificate, data: Data, type: Type, output: SerializationOutput) { - output.writeObject(obj.encoded, data, clazz) + override fun writeDescribedObject(obj: X509Certificate, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { + output.writeObject(obj.encoded, data, clazz, context) } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): X509Certificate { - val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ): X509Certificate { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray return X509CertificateFactory().generateCertificate(bits.inputStream()) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearMonthSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearMonthSerializer.kt index 1b97e1d131..8bb5458e82 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearMonthSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearMonthSerializer.kt @@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.YearMonth /** * A serializer for [YearMonth] that uses a proxy object to write out the integer form. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearSerializer.kt index f96c5ed7e9..2d2948c172 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/YearSerializer.kt @@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.Year /** * A serializer for [Year] that uses a proxy object to write out the integer form. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZonedDateTimeSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZonedDateTimeSerializer.kt index 9e851ac208..a411f8a955 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZonedDateTimeSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZonedDateTimeSerializer.kt @@ -2,7 +2,10 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime /** * A serializer for [ZonedDateTime] that uses a proxy object to write out the date, time, offset and zone. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index c400f94ff0..4dacba4f48 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -2,11 +2,11 @@ package net.corda.nodeapi.internal.serialization.carpenter +import net.corda.core.serialization.SerializationContext import net.corda.nodeapi.internal.serialization.amqp.CompositeType import net.corda.nodeapi.internal.serialization.amqp.RestrictedType import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema -import net.corda.core.serialization.SerializationContext fun AMQPSchema.carpenterSchema(classloader: ClassLoader): CarpenterMetaSchema { val rtn = CarpenterMetaSchema.newInstance() @@ -120,7 +120,7 @@ val typeStrToType: Map, Class> = mapOf( Pair("byte", false) to Byte::class.javaObjectType ) -fun String.stripGenerics() : String = if(this.endsWith('>')) { +fun String.stripGenerics(): String = if (this.endsWith('>')) { this.substring(0, this.indexOf('<')) } else this diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt index 070116c2d4..6dc7453a1a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt @@ -26,11 +26,11 @@ class CarpenterClassLoader(parentClassLoader: ClassLoader = Thread.currentThread fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) } -class InterfaceMismatchNonGetterException (val clazz: Class<*>, val method: Method) : InterfaceMismatchException( - "Requested interfaces must consist only of methods that start with 'get': ${clazz.name}.${method.name}") +class InterfaceMismatchNonGetterException(val clazz: Class<*>, val method: Method) : InterfaceMismatchException( + "Requested interfaces must consist only of methods that start with 'get': ${clazz.name}.${method.name}") -class InterfaceMismatchMissingAMQPFieldException (val clazz: Class<*>, val field: String) : InterfaceMismatchException( - "Interface ${clazz.name} requires a field named $field but that isn't found in the schema or any superclass schemas") +class InterfaceMismatchMissingAMQPFieldException(val clazz: Class<*>, val field: String) : InterfaceMismatchException( + "Interface ${clazz.name} requires a field named $field but that isn't found in the schema or any superclass schemas") /** * Which version of the java runtime are we constructing objects against diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Exceptions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Exceptions.kt index c7c2830c62..20710e12c5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Exceptions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Exceptions.kt @@ -9,18 +9,18 @@ import org.objectweb.asm.Type abstract class ClassCarpenterException(msg: String) : CordaRuntimeException(msg) /** - * Thrown by the [ClassCarpenter] when trying to build + * Thrown by the [ClassCarpenter] when trying to build */ abstract class InterfaceMismatchException(msg: String) : ClassCarpenterException(msg) -class DuplicateNameException(val name : String) : ClassCarpenterException ( +class DuplicateNameException(val name: String) : ClassCarpenterException( "An attempt was made to register two classes with the name '$name' within the same ClassCarpenter namespace.") class NullablePrimitiveException(val name: String, val field: Class) : ClassCarpenterException( "Field $name is primitive type ${Type.getDescriptor(field)} and thus cannot be nullable") class UncarpentableException(name: String, field: String, type: String) : - ClassCarpenterException("Class $name is loadable yet contains field $field of unknown type $type") + ClassCarpenterException("Class $name is loadable yet contains field $field of unknown type $type") /** * A meta exception used by the [MetaCarpenter] to wrap any exceptions generated during the build diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index 86316cc006..7a5111fd93 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -254,7 +254,7 @@ object NotaryChangeWireTransactionSerializer : Serializer): NotaryChangeWireTransaction { - val components : List = uncheckedCast(kryo.readClassAndObject(input)) + val components: List = uncheckedCast(kryo.readClassAndObject(input)) return NotaryChangeWireTransaction(components) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt index 7e1b94fffd..874219e1ec 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt @@ -1,6 +1,5 @@ package net.corda.nodeapi.internal.serialization.kryo -import java.util.concurrent.ConcurrentHashMap import co.paralleluniverse.fibers.Fiber import co.paralleluniverse.io.serialization.kryo.KryoSerializer import com.esotericsoftware.kryo.Kryo @@ -12,14 +11,14 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.pool.KryoPool import com.esotericsoftware.kryo.serializers.ClosureSerializer import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.ClassWhitelist +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializedBytes import net.corda.core.utilities.ByteSequence -import net.corda.core.serialization.* -import net.corda.nodeapi.internal.serialization.CordaSerializationMagic -import net.corda.nodeapi.internal.serialization.CordaClassResolver -import net.corda.nodeapi.internal.serialization.SectionId -import net.corda.nodeapi.internal.serialization.SerializationScheme import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.SectionId import java.security.PublicKey +import java.util.concurrent.ConcurrentHashMap val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0)) @@ -86,7 +85,8 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme { } override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - val dataBytes = kryoMagic.consume(byteSequence) ?: throw KryoException("Serialized bytes header does not match expected format.") + val dataBytes = kryoMagic.consume(byteSequence) + ?: throw KryoException("Serialized bytes header does not match expected format.") return context.kryo { kryoInput(ByteBufferInputStream(dataBytes)) { val result: T diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt index 1d26ba2703..d88d943a79 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt @@ -3,7 +3,10 @@ package net.corda.nodeapi.internal.serialization.kryo import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import net.corda.core.internal.LazyPool -import java.io.* +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.io.SequenceInputStream import java.nio.ByteBuffer class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt index eebe87a099..142e9fe35e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt @@ -14,12 +14,16 @@ import net.corda.core.serialization.SerializeAsToken */ class SerializeAsTokenSerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: T) { - kryo.writeClassAndObject(output, obj.toToken(kryo.serializationContext() ?: throw KryoException("Attempt to write a ${SerializeAsToken::class.simpleName} instance of ${obj.javaClass.name} without initialising a context"))) + kryo.writeClassAndObject(output, obj.toToken(kryo.serializationContext() + ?: throw KryoException("Attempt to write a ${SerializeAsToken::class.simpleName} instance of ${obj.javaClass.name} without initialising a context"))) } override fun read(kryo: Kryo, input: Input, type: Class): T { - val token = (kryo.readClassAndObject(input) as? SerializationToken) ?: throw KryoException("Non-token read for tokenized type: ${type.name}") - val fromToken = token.fromToken(kryo.serializationContext() ?: throw KryoException("Attempt to read a token for a ${SerializeAsToken::class.simpleName} instance of ${type.name} without initialising a context")) - return type.castIfPossible(fromToken) ?: throw KryoException("Token read ($token) did not return expected tokenized type: ${type.name}") + val token = (kryo.readClassAndObject(input) as? SerializationToken) + ?: throw KryoException("Non-token read for tokenized type: ${type.name}") + val fromToken = token.fromToken(kryo.serializationContext() + ?: throw KryoException("Attempt to read a token for a ${SerializeAsToken::class.simpleName} instance of ${type.name} without initialising a context")) + return type.castIfPossible(fromToken) + ?: throw KryoException("Token read ($token) did not return expected tokenized type: ${type.name}") } } \ No newline at end of file diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java index 11db9fc643..24d393b336 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java @@ -1,6 +1,8 @@ package net.corda.nodeapi.internal.serialization.amqp; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContextKt; import org.assertj.core.api.Assertions; import org.junit.Ignore; import org.junit.Test; @@ -41,7 +43,7 @@ public class ErrorMessageTests { SerializationOutput ser = new SerializationOutput(factory1); - Assertions.assertThatThrownBy(() -> ser.serialize(new C(1))) + Assertions.assertThatThrownBy(() -> ser.serialize(new C(1), TestSerializationContext.testSerializationContext)) .isInstanceOf(NotSerializableException.class) .hasMessage(errMsg("a", getClass().getName())); } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java index 9c206ad8cd..b595195f01 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp; import net.corda.core.serialization.SerializedBytes; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.junit.Test; import java.io.NotSerializableException; @@ -33,10 +34,10 @@ public class JavaGenericsTest { new SerializerFingerPrinter()); SerializationOutput ser = new SerializationOutput(factory); - SerializedBytes bytes = ser.serialize(a1); + SerializedBytes bytes = ser.serialize(a1, TestSerializationContext.testSerializationContext); DeserializationInput des = new DeserializationInput(factory); - A a2 = des.deserialize(bytes, A.class); + A a2 = des.deserialize(bytes, A.class, TestSerializationContext.testSerializationContext); assertEquals(1, a2.getT()); } @@ -48,13 +49,13 @@ public class JavaGenericsTest { new EvolutionSerializerGetter(), new SerializerFingerPrinter()); - return (new SerializationOutput(factory)).serialize(a); + return (new SerializationOutput(factory)).serialize(a, TestSerializationContext.testSerializationContext); } private SerializedBytes forceWildcardSerializeFactory( A a, SerializerFactory factory) throws NotSerializableException { - return (new SerializationOutput(factory)).serialize(a); + return (new SerializationOutput(factory)).serialize(a, TestSerializationContext.testSerializationContext); } private A forceWildcardDeserialize(SerializedBytes bytes) throws NotSerializableException { @@ -65,13 +66,14 @@ public class JavaGenericsTest { new SerializerFingerPrinter()); DeserializationInput des = new DeserializationInput(factory); - return des.deserialize(bytes, A.class); + return des.deserialize(bytes, A.class, TestSerializationContext.testSerializationContext); } private A forceWildcardDeserializeFactory( SerializedBytes bytes, SerializerFactory factory) throws NotSerializableException { - return (new DeserializationInput(factory)).deserialize(bytes, A.class); + return (new DeserializationInput(factory)).deserialize(bytes, A.class, + TestSerializationContext.testSerializationContext); } @Test diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedClassesTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedClassesTests.java index 66cd995220..80621a0a99 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedClassesTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedClassesTests.java @@ -6,6 +6,7 @@ import net.corda.core.contracts.ContractState; import net.corda.core.identity.AbstractParty; import net.corda.core.serialization.SerializedBytes; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.assertj.core.api.Assertions; import org.jetbrains.annotations.NotNull; import org.junit.Test; @@ -39,17 +40,17 @@ class OuterClass1 { } public void run() throws NotSerializableException { - SerializedBytes b = ser.serialize(new DummyState()); - desExisting.deserialize(b, DummyState.class); - desRegen.deserialize(b, DummyState.class); + SerializedBytes b = ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext); + desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); + desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); } } class Inherator1 extends OuterClass1 { public void iRun() throws NotSerializableException { - SerializedBytes b = ser.serialize(new DummyState()); - desExisting.deserialize(b, DummyState.class); - desRegen.deserialize(b, DummyState.class); + SerializedBytes b = ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext); + desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); + desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); } } @@ -85,17 +86,17 @@ class OuterClass2 { } public void run() throws NotSerializableException { - SerializedBytes b = ser.serialize(new DummyState(12)); - desExisting.deserialize(b, DummyState.class); - desRegen.deserialize(b, DummyState.class); + SerializedBytes b = ser.serialize(new DummyState(12), TestSerializationContext.testSerializationContext); + desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); + desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); } } class Inherator2 extends OuterClass2 { public void iRun() throws NotSerializableException { - SerializedBytes b = ser.serialize(new DummyState(12)); - desExisting.deserialize(b, DummyState.class); - desRegen.deserialize(b, DummyState.class); + SerializedBytes b = ser.serialize(new DummyState(12), TestSerializationContext.testSerializationContext); + desExisting.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); + desRegen.deserialize(b, DummyState.class, TestSerializationContext.testSerializationContext); } } @@ -120,7 +121,7 @@ abstract class AbstractClass2 { class Inherator4 extends AbstractClass2 { public void run() throws NotSerializableException { - ser.serialize(new DummyState()); + ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext); } } @@ -139,7 +140,7 @@ class Inherator5 extends AbstractClass3 { new SerializerFingerPrinter()); SerializationOutput ser = new SerializationOutput(factory); - ser.serialize(new DummyState()); + ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext); } } @@ -159,7 +160,7 @@ class Inherator6 extends AbstractClass3 { new SerializerFingerPrinter()); SerializationOutput ser = new SerializationOutput(factory); - ser.serialize(new Wrapper(new DummyState())); + ser.serialize(new Wrapper(new DummyState()), TestSerializationContext.testSerializationContext); } } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedInheritenceTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedInheritenceTests.java index 548f3625a3..8097fe9530 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedInheritenceTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaNestedInheritenceTests.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import net.corda.core.contracts.ContractState; import net.corda.core.identity.AbstractParty; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.assertj.core.api.Assertions; import org.jetbrains.annotations.NotNull; import org.junit.Test; @@ -38,7 +39,7 @@ public class JavaNestedInheritenceTests extends JavaNestedInheritenceTestsBase { SerializationOutput ser = new SerializationOutput(factory); - Assertions.assertThatThrownBy(() -> ser.serialize(new DummyState())).isInstanceOf( + Assertions.assertThatThrownBy(() -> ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext)).isInstanceOf( NotSerializableException.class).hasMessageContaining( "has synthetic fields and is likely a nested inner class"); } @@ -50,7 +51,7 @@ public class JavaNestedInheritenceTests extends JavaNestedInheritenceTestsBase { new SerializerFingerPrinter()); SerializationOutput ser = new SerializationOutput(factory); - Assertions.assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()))).isInstanceOf( + Assertions.assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf( NotSerializableException.class).hasMessageContaining( "has synthetic fields and is likely a nested inner class"); } @@ -63,7 +64,7 @@ public class JavaNestedInheritenceTests extends JavaNestedInheritenceTestsBase { SerializationOutput ser = new SerializationOutput(factory1); - Assertions.assertThatThrownBy(() -> ser.serialize(new TemplateWrapper (new DummyState()))).isInstanceOf( + Assertions.assertThatThrownBy(() -> ser.serialize(new TemplateWrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf( NotSerializableException.class).hasMessageContaining( "has synthetic fields and is likely a nested inner class"); } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java index 2ee89945f6..75e6d44ea7 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.junit.Test; import static org.junit.Assert.*; @@ -83,7 +84,7 @@ public class JavaPrivatePropertyTests { DeserializationInput des = new DeserializationInput(factory); B b = new B(true); - B b2 = des.deserialize(ser.serialize(b), B.class); + B b2 = des.deserialize(ser.serialize(b, TestSerializationContext.testSerializationContext), B.class, TestSerializationContext.testSerializationContext); assertEquals (b.b, b2.b); } @@ -98,7 +99,7 @@ public class JavaPrivatePropertyTests { B2 b = new B2(); b.setB(false); - B2 b2 = des.deserialize(ser.serialize(b), B2.class); + B2 b2 = des.deserialize(ser.serialize(b, TestSerializationContext.testSerializationContext), B2.class, TestSerializationContext.testSerializationContext); assertEquals (b.b, b2.b); } @@ -112,7 +113,7 @@ public class JavaPrivatePropertyTests { B3 b = new B3(); b.setB(false); - B3 b2 = des.deserialize(ser.serialize(b), B3.class); + B3 b2 = des.deserialize(ser.serialize(b, TestSerializationContext.testSerializationContext), B3.class, TestSerializationContext.testSerializationContext); // since we can't find a getter for b (isb != isB) then we won't serialize that parameter assertEquals (null, b2.b); @@ -128,7 +129,7 @@ public class JavaPrivatePropertyTests { C3 c = new C3(); c.setA(12345); - C3 c2 = des.deserialize(ser.serialize(c), C3.class); + C3 c2 = des.deserialize(ser.serialize(c, TestSerializationContext.testSerializationContext), C3.class, TestSerializationContext.testSerializationContext); assertEquals (c.a, c2.a); } @@ -143,7 +144,7 @@ public class JavaPrivatePropertyTests { DeserializationInput des = new DeserializationInput(factory); C c = new C("dripping taps"); - C c2 = des.deserialize(ser.serialize(c), C.class); + C c2 = des.deserialize(ser.serialize(c, TestSerializationContext.testSerializationContext), C.class, TestSerializationContext.testSerializationContext); assertEquals (c.a, c2.a); @@ -175,7 +176,7 @@ public class JavaPrivatePropertyTests { DeserializationInput des = new DeserializationInput(factory); C2 c = new C2("dripping taps"); - C2 c2 = des.deserialize(ser.serialize(c), C2.class); + C2 c2 = des.deserialize(ser.serialize(c, TestSerializationContext.testSerializationContext), C2.class, TestSerializationContext.testSerializationContext); assertEquals (c.a, c2.a); diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java index 9be95ca396..e55fe7caed 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.junit.Test; import net.corda.nodeapi.internal.serialization.AllWhitelist; @@ -33,6 +34,6 @@ public class JavaSerialiseEnumTests { new EvolutionSerializerGetter(), new SerializerFingerPrinter()); SerializationOutput ser = new SerializationOutput(factory1); - SerializedBytes bytes = ser.serialize(bra); + SerializedBytes bytes = ser.serialize(bra, TestSerializationContext.testSerializationContext); } } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java index e62775218b..f32254110c 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java @@ -6,6 +6,7 @@ import net.corda.core.identity.AbstractParty; import net.corda.core.serialization.ConstructorForDeserialization; import net.corda.nodeapi.internal.serialization.AllWhitelist; import net.corda.core.serialization.SerializedBytes; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.apache.qpid.proton.codec.DecoderImpl; import org.apache.qpid.proton.codec.EncoderImpl; import org.jetbrains.annotations.NotNull; @@ -189,7 +190,7 @@ public class JavaSerializationOutputTests { evolutionSerialiserGetter, fingerPrinter); SerializationOutput ser = new SerializationOutput(factory1); - SerializedBytes bytes = ser.serialize(obj); + SerializedBytes bytes = ser.serialize(obj, TestSerializationContext.testSerializationContext); DecoderImpl decoder = new DecoderImpl(); @@ -209,13 +210,15 @@ public class JavaSerializationOutputTests { assertTrue(result != null); DeserializationInput des = new DeserializationInput(factory2); - Object desObj = des.deserialize(bytes, Object.class); + Object desObj = des.deserialize(bytes, Object.class, TestSerializationContext.testSerializationContext); assertTrue(Objects.deepEquals(obj, desObj)); // Now repeat with a re-used factory SerializationOutput ser2 = new SerializationOutput(factory1); DeserializationInput des2 = new DeserializationInput(factory1); - Object desObj2 = des2.deserialize(ser2.serialize(obj), Object.class); + Object desObj2 = des2.deserialize(ser2.serialize(obj, TestSerializationContext.testSerializationContext), + Object.class, TestSerializationContext.testSerializationContext); + assertTrue(Objects.deepEquals(obj, desObj2)); // TODO: check schema is as expected return desObj2; diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java index 153681fa6a..8397f5a9f8 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java @@ -3,6 +3,7 @@ package net.corda.nodeapi.internal.serialization.amqp; import net.corda.core.serialization.CordaSerializable; import net.corda.core.serialization.SerializedBytes; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.junit.Assert; import org.junit.Test; @@ -132,9 +133,9 @@ public class ListsSerializationJavaTest { evolutionSerializerGetter, fingerPrinter); SerializationOutput ser = new SerializationOutput(factory1); - SerializedBytes bytes = ser.serialize(container); + SerializedBytes bytes = ser.serialize(container, TestSerializationContext.testSerializationContext); DeserializationInput des = new DeserializationInput(factory1); - T deserialized = des.deserialize(bytes, clazz); + T deserialized = des.deserialize(bytes, clazz, TestSerializationContext.testSerializationContext); Assert.assertEquals(container, deserialized); } } \ No newline at end of file diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java index e9ac48b063..e68e4c9b85 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp; import net.corda.core.serialization.SerializedBytes; import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationContext; import org.assertj.core.api.Assertions; import org.junit.Test; import static org.junit.Assert.*; @@ -132,7 +133,7 @@ public class SetterConstructorTests { c1.setA(1); c1.setB(2); c1.setC(3); - Schema schemas = ser.serializeAndReturnSchema(c1).component2(); + Schema schemas = ser.serializeAndReturnSchema(c1, TestSerializationContext.testSerializationContext).component2(); assertEquals(1, schemas.component1().size()); assertEquals(this.getClass().getName() + "$C", schemas.component1().get(0).getName()); @@ -147,7 +148,7 @@ public class SetterConstructorTests { C2 c2 = new C2(); c2.setA(1); c2.setB(2); - schemas = ser.serializeAndReturnSchema(c2).component2(); + schemas = ser.serializeAndReturnSchema(c2, TestSerializationContext.testSerializationContext).component2(); assertEquals(1, schemas.component1().size()); assertEquals(this.getClass().getName() + "$C2", schemas.component1().get(0).getName()); @@ -164,7 +165,7 @@ public class SetterConstructorTests { c3.setA(1); c3.setB(2); c3.setC(3); - schemas = ser.serializeAndReturnSchema(c3).component2(); + schemas = ser.serializeAndReturnSchema(c3, TestSerializationContext.testSerializationContext).component2(); assertEquals(1, schemas.component1().size()); assertEquals(this.getClass().getName() + "$C3", schemas.component1().get(0).getName()); @@ -180,7 +181,7 @@ public class SetterConstructorTests { c4.setA(1); c4.setB(2); c4.setC(3); - schemas = ser.serializeAndReturnSchema(c4).component2(); + schemas = ser.serializeAndReturnSchema(c4, TestSerializationContext.testSerializationContext).component2(); assertEquals(1, schemas.component1().size()); assertEquals(this.getClass().getName() + "$C4", schemas.component1().get(0).getName()); @@ -212,9 +213,9 @@ public class SetterConstructorTests { cPre1.setB(b); cPre1.setC(c); - SerializedBytes bytes = new SerializationOutput(factory1).serialize(cPre1); + SerializedBytes bytes = new SerializationOutput(factory1).serialize(cPre1, TestSerializationContext.testSerializationContext); - C cPost1 = new DeserializationInput(factory1).deserialize(bytes, C.class); + C cPost1 = new DeserializationInput(factory1).deserialize(bytes, C.class, TestSerializationContext.testSerializationContext); assertEquals(a, cPost1.a); assertEquals(b, cPost1.b); @@ -224,8 +225,8 @@ public class SetterConstructorTests { cPre2.setA(1); cPre2.setB(2); - C2 cPost2 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre2), - C2.class); + C2 cPost2 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre2, TestSerializationContext.testSerializationContext), + C2.class, TestSerializationContext.testSerializationContext); assertEquals(a, cPost2.a); assertEquals(b, cPost2.b); @@ -239,8 +240,9 @@ public class SetterConstructorTests { cPre3.setB(2); cPre3.setC(3); - C3 cPost3 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre3), - C3.class); + C3 cPost3 = new DeserializationInput(factory1).deserialize( + new SerializationOutput(factory1).serialize(cPre3, TestSerializationContext.testSerializationContext), + C3.class, TestSerializationContext.testSerializationContext); assertEquals(a, cPost3.a); @@ -253,8 +255,11 @@ public class SetterConstructorTests { cPre4.setB(2); cPre4.setC(3); - C4 cPost4 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre4), - C4.class); + C4 cPost4 = new DeserializationInput(factory1).deserialize( + new SerializationOutput(factory1).serialize(cPre4, + TestSerializationContext.testSerializationContext), + C4.class, + TestSerializationContext.testSerializationContext); assertEquals(0, cPost4.a); assertEquals(0, cPost4.b); @@ -280,8 +285,10 @@ public class SetterConstructorTests { o.setB("World"); o.setC(i2); - Outer post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(o), - Outer.class); + Outer post = new DeserializationInput(factory1).deserialize( + new SerializationOutput(factory1).serialize( + o, TestSerializationContext.testSerializationContext), + Outer.class, TestSerializationContext.testSerializationContext); assertEquals("Hello", post.a.a); assertEquals("World", post.b); @@ -290,7 +297,7 @@ public class SetterConstructorTests { } @Test - public void typeMistmatch() throws NotSerializableException { + public void typeMistmatch() { EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); FingerPrinter fingerPrinter = new SerializerFingerPrinter(); SerializerFactory factory1 = new SerializerFactory( @@ -303,12 +310,13 @@ public class SetterConstructorTests { tm.setA(10); assertEquals("10", tm.getA()); - Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm)).isInstanceOf ( + Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm, + TestSerializationContext.testSerializationContext)).isInstanceOf ( NotSerializableException.class); } @Test - public void typeMistmatch2() throws NotSerializableException { + public void typeMistmatch2() { EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); FingerPrinter fingerPrinter = new SerializerFingerPrinter(); SerializerFactory factory1 = new SerializerFactory( @@ -321,7 +329,8 @@ public class SetterConstructorTests { tm.setA("10"); assertEquals((Integer)10, tm.getA()); - Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm)).isInstanceOf( + Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm, + TestSerializationContext.testSerializationContext)).isInstanceOf( NotSerializableException.class); } @@ -347,6 +356,10 @@ public class SetterConstructorTests { // if we've got super / sub types on the setter vs the underlying type the wrong way around this will // explode. See CORDA-1229 (https://r3-cev.atlassian.net/browse/CORDA-1229) - new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cil), CIntList.class); + new DeserializationInput(factory1).deserialize( + new SerializationOutput(factory1).serialize( + cil, TestSerializationContext.testSerializationContext), + CIntList.class, + TestSerializationContext.testSerializationContext); } } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.java new file mode 100644 index 0000000000..126ff34f71 --- /dev/null +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.java @@ -0,0 +1,24 @@ +package net.corda.nodeapi.internal.serialization.amqp.testutils; + +import net.corda.core.serialization.SerializationContext; +import net.corda.core.utilities.ByteSequence; +import net.corda.nodeapi.internal.serialization.AllWhitelist; +import net.corda.nodeapi.internal.serialization.SerializationContextImpl; + + +import java.util.HashMap; +import java.util.Map; + +public class TestSerializationContext { + + static private Map serializationProperties = new HashMap(); + + public static SerializationContext testSerializationContext = new SerializationContextImpl( + ByteSequence.of(new byte[] { 'c', 'o', 'r', 'd', 'a', (byte)0, (byte)0, (byte)1}), + ClassLoader.getSystemClassLoader(), + AllWhitelist.INSTANCE, + serializationProperties, + false, + SerializationContext.UseCase.Testing, + null); +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt deleted file mode 100644 index d7701aed52..0000000000 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt +++ /dev/null @@ -1,32 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp - -import org.apache.qpid.proton.codec.Data -import net.corda.nodeapi.internal.serialization.AllWhitelist -import net.corda.nodeapi.internal.serialization.EmptyWhitelist - -fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) -fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(), - EvolutionSerializerGetterTesting()) -fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()) - -class TestSerializationOutput( - private val verbose: Boolean, - serializerFactory: SerializerFactory = testDefaultFactory()) - : SerializationOutput(serializerFactory) { - - override fun writeSchema(schema: Schema, data: Data) { - if (verbose) println(schema) - super.writeSchema(schema, data) - } - - override fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) { - if(verbose) { - println ("Writing Transform Schema") - println (transformsSchema) - } - super.writeTransformSchema(transformsSchema, data) - } -} - -fun testName(): String = Thread.currentThread().stackTrace[2].methodName - diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt index d809f7beda..8a4367792d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt @@ -3,9 +3,15 @@ package net.corda.nodeapi.internal.serialization.amqp import org.junit.Test import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactory import org.assertj.core.api.Assertions import java.io.NotSerializableException import kotlin.test.assertEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize + class CorDappSerializerTests { data class NeedsProxy (val a: String) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt index d793c9f0d2..f6a3f90439 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt @@ -1,10 +1,17 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.CordaSerializable +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryWithWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.testName import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertTrue +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class DeserializeAndReturnEnvelopeTests { // the 'this' reference means we can't just move this to the common test utils diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt index 2e910bf19c..58a2d33bd1 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt @@ -1,8 +1,14 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution import org.assertj.core.api.Assertions import org.junit.Test import java.util.* +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class DeserializeMapTests { companion object { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt index caa3e06c4f..c3855c73b2 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt @@ -1,9 +1,16 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryWithWhitelist import org.junit.Test import kotlin.test.* import net.corda.nodeapi.internal.serialization.carpenter.* +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) { companion object { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt index 002adc5e24..c7a6b80913 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt @@ -1,9 +1,15 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution import org.junit.Test import kotlin.test.* import net.corda.nodeapi.internal.serialization.carpenter.* +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize // These tests work by having the class carpenter build the classes we serialise and then deserialise. Because // those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index f54112b2dc..c733422c8f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -5,6 +5,11 @@ import org.junit.Test import kotlin.test.* import net.corda.nodeapi.internal.serialization.carpenter.* import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryWithWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize @CordaSerializable interface I { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt index 6f2dcd3bc2..85aeece991 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt @@ -1,7 +1,11 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution import org.junit.Test import kotlin.test.assertEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize // Prior to certain fixes being made within the [PropertySerializaer] classes these simple // deserialization operations would've blown up with type mismatch errors where the deserlized diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt index 7e898aa46d..c2d919640d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt @@ -1,6 +1,8 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactory import net.corda.testing.common.internal.ProjectStructure.projectRootDir import org.assertj.core.api.Assertions import org.junit.Test @@ -10,6 +12,9 @@ import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals import kotlin.test.assertTrue +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope class EnumEvolvabilityTests { @Suppress("UNUSED") diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolveTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolveTests.kt index 146eba4d2f..c19a1e8c42 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolveTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolveTests.kt @@ -4,6 +4,8 @@ import net.corda.core.internal.toPath import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.SerializedBytes +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactory +import net.corda.nodeapi.internal.serialization.amqp.testutils.testName import net.corda.testing.common.internal.ProjectStructure.projectRootDir import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -12,6 +14,8 @@ import java.io.NotSerializableException import java.net.URI import kotlin.test.assertEquals import kotlin.test.assertNotNull +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize // NOTE: To recreate the test files used by these tests uncomment the original test classes and comment // the new ones out, then change each test to write out the serialized bytes rather than read diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt index 5cb337efa7..bf2c295f5c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt @@ -3,12 +3,19 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution +import net.corda.nodeapi.internal.serialization.amqp.testutils.testName import org.assertj.core.api.Assertions import org.junit.Test import java.io.NotSerializableException import java.time.DayOfWeek import kotlin.test.assertEquals import kotlin.test.assertNotNull +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class EnumTests { enum class Bras { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt index 6aab88660a..d0c6ac10ce 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt @@ -1,9 +1,16 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactory +import net.corda.nodeapi.internal.serialization.amqp.testutils.testName import org.assertj.core.api.Assertions import org.junit.Ignore import org.junit.Test import java.io.NotSerializableException +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class ErrorMessagesTests { companion object { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index d126cd1df8..a0ab73d26c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -8,6 +8,8 @@ import net.corda.core.node.NotaryInfo import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactory import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.TestIdentity @@ -18,6 +20,8 @@ import java.io.NotSerializableException import java.net.URI import java.time.Instant import kotlin.test.assertEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize // To regenerate any of the binary test files do the following // diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/FingerPrinterTesting.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/FingerPrinterTesting.kt index 9b5ceb24df..bb3d4517c9 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/FingerPrinterTesting.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/FingerPrinterTesting.kt @@ -4,6 +4,8 @@ import org.junit.Test import java.lang.reflect.Type import kotlin.test.assertEquals import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema class FingerPrinterTesting : FingerPrinter { private var index = 0 diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index 21a26dde5d..41602754a4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -7,12 +7,8 @@ import net.corda.testing.common.internal.ProjectStructure.projectRootDir import org.junit.Test import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party -import net.corda.core.transactions.WireTransaction +import net.corda.nodeapi.internal.serialization.amqp.testutils.* import net.corda.testing.core.TestIdentity -import org.hibernate.Transaction -import java.io.File -import java.net.URI import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals @@ -301,7 +297,6 @@ class GenericsTests { val factory4 = SerializerFactory(AllWhitelist, cl()) factory4.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) val des2 = DeserializationInput(factory4).deserializeAndReturnEnvelope(ser2.obj) - } @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt index a08bcb5a3c..2eed1e0e00 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt @@ -13,11 +13,15 @@ class OverridePKSerializerTest { class SerializerTestException(message: String) : Exception(message) class TestPublicKeySerializer : CustomSerializer.Implements(PublicKey::class.java) { - override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput) { + override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput, + context: SerializationContext + ) { throw SerializerTestException("Custom write call") } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): PublicKey { + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, + context: SerializationContext + ) : PublicKey { throw SerializerTestException("Custom read call") } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt index 795ac1a12e..820b9d3171 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt @@ -3,11 +3,15 @@ package net.corda.nodeapi.internal.serialization.amqp import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertEquals import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution import org.junit.Test import org.apache.qpid.proton.amqp.Symbol import org.assertj.core.api.Assertions import java.io.NotSerializableException import java.util.concurrent.ConcurrentHashMap +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class PrivatePropertyTests { private val factory = testDefaultFactoryNoEvolution() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt index f42823a862..aeff6be5bd 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt @@ -1,8 +1,11 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution import org.assertj.core.api.Assertions import org.junit.Test +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class RoundTripTests { @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 19fddfafe0..290b5e89f6 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -23,6 +23,7 @@ import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.ContentSignerBuilder import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive +import net.corda.nodeapi.internal.serialization.amqp.testutils.* import net.corda.testing.contracts.DummyContract import net.corda.testing.core.BOB_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -480,12 +481,12 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi // Double check copy[valueIndex] = 0x03 - assertThat(des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java).value).isEqualTo(3) + assertThat(des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java, testSerializationContext).value).isEqualTo(3) // Now use the forbidden value copy[valueIndex] = 0x00 assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy { - des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java) + des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java, testSerializationContext) }.withMessageContaining("Zero not allowed") } @@ -650,14 +651,16 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi val scheme = AMQPServerSerializationScheme(emptyList()) val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" } - .java.getDeclaredMethod("registerCustomSerializers", SerializerFactory::class.java) + .java.getDeclaredMethod("registerCustomSerializers", + SerializationContext::class.java, + SerializerFactory::class.java) func.isAccessible = true val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - func.invoke(scheme, factory) + func.invoke(scheme, testSerializationContext, factory) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - func.invoke(scheme, factory2) + func.invoke(scheme, testSerializationContext, factory2) val desState = serdes(state, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) assertTrue((desState as TransactionState<*>).data is FooState) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationPropertyOrdering.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationPropertyOrdering.kt index 2fd435aa24..539fed083a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationPropertyOrdering.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationPropertyOrdering.kt @@ -1,6 +1,8 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution import org.junit.Test import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals @@ -8,6 +10,10 @@ import org.apache.qpid.proton.amqp.Symbol import java.lang.reflect.Method import kotlin.test.assertNotNull import kotlin.test.assertTrue +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class SerializationPropertyOrdering { companion object { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt index 51248a4d4e..b6d8255927 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt @@ -1,9 +1,12 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactoryNoEvolution +import net.corda.nodeapi.internal.serialization.amqp.testutils.testName import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema class SerializeAndReturnSchemaTest { // the 'this' reference means we can't just move this to the common test utils diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt index d303f0dd52..8468bcb4b1 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt @@ -10,6 +10,10 @@ import java.io.NotSerializableException import java.lang.reflect.Type import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.serializeAndReturnSchema +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize class InStatic : Exception("Help!, help!, I'm being repressed") diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/AMQPTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/AMQPTestUtils.kt new file mode 100644 index 0000000000..e8a02a80b4 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/AMQPTestUtils.kt @@ -0,0 +1,67 @@ +package net.corda.nodeapi.internal.serialization.amqp.testutils + +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializedBytes +import org.apache.qpid.proton.codec.Data +import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.EmptyWhitelist +import net.corda.nodeapi.internal.serialization.amqp.* +import java.io.NotSerializableException + +fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) +fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(), + EvolutionSerializerGetterTesting()) +fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()) + +class TestSerializationOutput( + private val verbose: Boolean, + serializerFactory: SerializerFactory = testDefaultFactory()) + : SerializationOutput(serializerFactory) { + + override fun writeSchema(schema: Schema, data: Data) { + if (verbose) println(schema) + super.writeSchema(schema, data) + } + + override fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) { + if(verbose) { + println ("Writing Transform Schema") + println (transformsSchema) + } + super.writeTransformSchema(transformsSchema, data) + } +} + +fun testName(): String = Thread.currentThread().stackTrace[2].methodName + + +@Throws(NotSerializableException::class) +inline fun DeserializationInput.deserializeAndReturnEnvelope( + bytes: SerializedBytes, + context: SerializationContext? = null +) : ObjectAndEnvelope { + return deserializeAndReturnEnvelope(bytes, T::class.java, + context ?: testSerializationContext) +} + +@Throws(NotSerializableException::class) +inline fun DeserializationInput.deserialize( + bytes: SerializedBytes, + context: SerializationContext? = null +) : T = deserialize(bytes, T::class.java, context ?: testSerializationContext) + + +@Throws(NotSerializableException::class) +fun SerializationOutput.serializeAndReturnSchema( + obj: T, context: SerializationContext? = null +): BytesAndSchemas = serializeAndReturnSchema(obj, context ?: testSerializationContext) + + +@Throws(NotSerializableException::class) +fun SerializationOutput.serialize(obj: T): SerializedBytes { + try { + return _serialize(obj, testSerializationContext) + } finally { + andFinally() + } +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.kt new file mode 100644 index 0000000000..237640a59a --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/testutils/TestSerializationContext.kt @@ -0,0 +1,17 @@ +package net.corda.nodeapi.internal.serialization.amqp.testutils + +import net.corda.core.serialization.SerializationContext +import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.SerializationContextImpl +import net.corda.nodeapi.internal.serialization.amqp.amqpMagic + +val serializationProperties: MutableMap = mutableMapOf() + +val testSerializationContext = SerializationContextImpl( + preferredSerializationVersion = amqpMagic, + deserializationClassLoader = ClassLoader.getSystemClassLoader(), + whitelist = AllWhitelist, + properties = serializationProperties, + objectReferencesEnabled = false, + useCase = SerializationContext.UseCase.Testing, + encoding = null) \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CarpenterExceptionTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CarpenterExceptionTests.kt index c3ce2469a5..388f20517d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CarpenterExceptionTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CarpenterExceptionTests.kt @@ -4,6 +4,8 @@ import com.google.common.reflect.TypeToken import junit.framework.Assert.assertTrue import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.amqp.* +import net.corda.nodeapi.internal.serialization.amqp.testutils.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.amqp.testutils.testDefaultFactory import org.assertj.core.api.Assertions import org.junit.Test import java.io.NotSerializableException @@ -11,6 +13,8 @@ import java.lang.reflect.Type import java.net.URL import kotlin.reflect.jvm.jvmName import kotlin.test.assertEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserialize // Simple way to ensure we end up trying to carpent a class, "remove" it from the class loader (if only // actually doing that was simple) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt index a27b6440d3..87c4ce8854 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -4,7 +4,7 @@ import net.corda.core.serialization.ClassWhitelist import net.corda.nodeapi.internal.serialization.amqp.* import net.corda.nodeapi.internal.serialization.amqp.Field import net.corda.nodeapi.internal.serialization.amqp.Schema -import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.amqp.testutils.serialize fun mangleName(name: String) = "${name}__carpenter" diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index e9e42e1cf4..992bcd6dff 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -8,6 +8,7 @@ import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope @CordaSerializable interface I_ { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt index cbd35f8a3b..3f9111af3a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -5,6 +5,7 @@ import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import org.junit.Test import kotlin.test.* +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope @CordaSerializable interface J { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index bc4cafdbb0..ad87f0c801 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -7,6 +7,7 @@ import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 2035afe5a7..6c714b48ac 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -6,6 +6,7 @@ import net.corda.nodeapi.internal.serialization.amqp.CompositeType import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import org.junit.Test import kotlin.test.assertEquals +import net.corda.nodeapi.internal.serialization.amqp.testutils.deserializeAndReturnEnvelope class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { @Test From fe87f16d985571e98c9ed2374efdde281b9812e8 Mon Sep 17 00:00:00 2001 From: cburlinchon Date: Thu, 3 May 2018 16:24:54 +0100 Subject: [PATCH 05/18] Register crypto providers after we init logging (#3067) * Register crypto providers after we init logging --- node/src/main/kotlin/net/corda/node/Corda.kt | 5 ----- node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/Corda.kt b/node/src/main/kotlin/net/corda/node/Corda.kt index 2c820e992f..5889aacdc7 100644 --- a/node/src/main/kotlin/net/corda/node/Corda.kt +++ b/node/src/main/kotlin/net/corda/node/Corda.kt @@ -3,15 +3,10 @@ package net.corda.node -import net.corda.core.crypto.CordaSecurityProvider -import net.corda.core.crypto.Crypto import net.corda.node.internal.NodeStartup import kotlin.system.exitProcess fun main(args: Array) { - // Register all cryptography [Provider]s first thing on boot. - // Required to install our [SecureRandom] before e.g., UUID asks for one. - Crypto.registerProviders() // Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass. // It will exit the process in case of startup failure and is not intended to be used by embedders. If you want // to embed Node in your own container, instantiate it directly and set up the configuration objects yourself. diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 19a58b25fa..3a654b77a7 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -1,6 +1,7 @@ package net.corda.node.internal import com.jcabi.manifests.Manifests +import net.corda.core.crypto.Crypto import net.corda.core.internal.Emoji import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.createDirectories @@ -55,6 +56,11 @@ open class NodeStartup(val args: Array) { initLogging(cmdlineOptions) + // Register all cryptography [Provider]s. + // Required to install our [SecureRandom] before e.g., UUID asks for one. + // This needs to go after initLogging(netty clashes with our logging). + Crypto.registerProviders() + val versionInfo = getVersionInfo() if (cmdlineOptions.isVersion) { From a772884d48bc273c6cc154620eab954d543ab3ae Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 3 May 2018 17:48:49 +0100 Subject: [PATCH 06/18] CORDA-1030 - allow nodes to be waited for in-process (#2678) (#3070) (cherry picked from commit 53bb986) --- .../net/corda/testing/driver/DriverTests.kt | 37 ++++++++++++++++++- .../testing/node/internal/DriverDSLImpl.kt | 24 ++++++++++-- .../testing/node/internal/ShutdownManager.kt | 11 +++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index dd8a94dc4b..7e9ee5dcf7 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -3,6 +3,8 @@ package net.corda.testing.driver import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name import net.corda.core.internal.CertRole +import net.corda.core.internal.concurrent.fork +import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.div import net.corda.core.internal.list @@ -11,7 +13,10 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup import net.corda.testing.common.internal.ProjectStructure.projectRootDir -import net.corda.testing.core.* +import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.DUMMY_BANK_A_NAME +import net.corda.testing.core.DUMMY_BANK_B_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.driver.internal.RandomFree import net.corda.testing.http.HttpApi import net.corda.testing.node.NotarySpec @@ -21,9 +26,13 @@ import net.corda.testing.node.internal.internalDriver import org.assertj.core.api.Assertions.* import org.json.simple.JSONObject import org.junit.Test +import java.util.* +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors +import java.util.concurrent.ForkJoinPool import java.util.concurrent.ScheduledExecutorService import kotlin.streams.toList +import kotlin.test.assertEquals class DriverTests { private companion object { @@ -166,5 +175,31 @@ class DriverTests { } } + + @Test + fun `driver waits for in-process nodes to finish`() { + fun NodeHandle.stopQuietly() = try { + stop() + } catch (t: Throwable) { + t.printStackTrace() + } + + val handlesFuture = openFuture>() + val driverExit = CountDownLatch(1) + val testFuture = ForkJoinPool.commonPool().fork { + val handles = LinkedList(handlesFuture.getOrThrow()) + val last = handles.removeLast() + handles.forEach { it.stopQuietly() } + assertEquals(1, driverExit.count) + last.stopQuietly() + } + driver(DriverParameters(startNodesInProcess = true, waitForAllNodesToFinish = true)) { + val nodeA = newNode(DUMMY_BANK_A_NAME)().getOrThrow() + handlesFuture.set(listOf(nodeA) + notaryHandles.map { it.nodeHandles.getOrThrow() }.flatten()) + } + driverExit.countDown() + testFuture.getOrThrow() + } + private fun DriverDSL.newNode(name: CordaX500Name) = { startNode(NodeParameters(providedName = name)) } } \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 80a282fd0a..c561805c88 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -72,6 +72,7 @@ import java.util.* import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit +import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.concurrent.thread import net.corda.nodeapi.internal.config.User as InternalUser @@ -107,8 +108,13 @@ class DriverDSLImpl( private lateinit var _notaries: CordaFuture> override val notaryHandles: List get() = _notaries.getOrThrow() + interface Waitable { + @Throws(InterruptedException::class) + fun waitFor(): Unit + } + class State { - val processes = ArrayList() + val processes = ArrayList() } private val state = ThreadBox(State()) @@ -617,20 +623,32 @@ class DriverDSLImpl( } } ) - return nodeAndThreadFuture.flatMap { (node, thread) -> + val nodeFuture: CordaFuture = nodeAndThreadFuture.flatMap { (node, thread) -> establishRpc(config, openFuture()).flatMap { rpc -> visibilityHandle.listen(rpc).map { InProcessImpl(rpc.nodeInfo(), rpc, config.corda, webAddress, useHTTPS, thread, onNodeExit, node) } } } + state.locked { + processes += object : Waitable { + override fun waitFor() { + nodeAndThreadFuture.getOrThrow().second.join() + } + } + } + return nodeFuture } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize) if (waitForAllNodesToFinish) { state.locked { - processes += process + processes += object : Waitable { + override fun waitFor() { + process.waitFor() + } + } } } else { shutdownManager.registerProcessShutdown(process) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt index 6e5f7c02af..433cff4c00 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt @@ -3,7 +3,10 @@ package net.corda.testing.node.internal import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.ThreadBox import net.corda.core.internal.concurrent.doneFuture -import net.corda.core.utilities.* +import net.corda.core.utilities.Try +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeoutException @@ -12,6 +15,7 @@ import java.util.concurrent.atomic.AtomicInteger class ShutdownManager(private val executorService: ExecutorService) { private class State { val registeredShutdowns = ArrayList Unit>>() + var isShuttingDown = false var isShutdown = false } @@ -32,6 +36,7 @@ class ShutdownManager(private val executorService: ExecutorService) { } fun shutdown() { + state.locked { isShuttingDown = true } val shutdownActionFutures = state.locked { if (isShutdown) { emptyList Unit>>() @@ -101,4 +106,8 @@ class ShutdownManager(private val executorService: ExecutorService) { } } } + + fun isShuttingDown(): Boolean { + return state.locked { isShuttingDown } + } } \ No newline at end of file From 3e65ab1a7e1cede932b35844c8938526fdfb948e Mon Sep 17 00:00:00 2001 From: Richard Gendal Brown Date: Fri, 4 May 2018 09:18:36 +0100 Subject: [PATCH 07/18] Expanded message to contributors (#2969) * Revised and expanded contribution guidelines * Added some extra links and reordered * Reworded substantially following Dave Hudson's feedback. Incorporated James's feedback too * fixed urls * capital letter after colons if they are the start of a new sentence * Named Joel as a maintainer * Linked to docsite mechanics page * simplified some of the wording * Said R3 Alliance the first time R3 is mentioned and clarified that R3 implies R3 Alliance * merged the two "how to contribute" sections --- CONTRIBUTING.md | 79 +++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6582fe405d..4aff95e6d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,52 +1,67 @@ # Contributing to Corda -To start contributing you can fork our repo and begin making pull requests. Please use -descriptive commit messages and follow our [coding style guidelines](https://docs.corda.net/codestyle.html). +## Contributions are Welcome + +We welcome contributions to Corda! Here's how to go about it. + +## Mission + +Corda is an open source project with the aim of developing an enterprise-grade distributed ledger platform for business across a variety of industries. Corda was designed and developed to apply the concepts of blockchain and smart contract technologies to the requirements of modern business transactions. It is unique in its aim to build a platform for businesses to transact freely with any counter-party while retaining strict privacy. Corda provides an implementation of this vision in a code base which others are free to build on, contribute to or innovate around. The mission of Corda is further detailed in the [Corda introductory white paper](https://docs.corda.net/_static/corda-introductory-whitepaper.pdf). + +The project is supported and maintained by the [R3 Alliance](https://www.r3.com), or R3 for short, which consists of over two hundred firms working together to build and maintain this open source enterprise-grade blockchain platform. + +## Scope + +We believe one of the things that makes Corda special is its coherent design and we seek to retain this defining characteristic. One of the ways we do this is by encouraging developers of new features to write a design proposal for review, comment and advice before they start work and you are encouraged to take advantage of this process. Posting to the [design](https://cordaledger.slack.com/messages/C3J04VC3V/) channel on Slack is a good way to kick this process off. When reviewing a proposed feature, we use the [Corda Technical Whitepaper](https://docs.corda.net/_static/corda-technical-whitepaper.pdf) as a guide and you are strongly encouraged to study it. This white paper is a living document that is updated from time to time under the guidance of the project leader (see below). + +## How to Contribute + +Contributions from the community are welcomed and strongly encouraged. From the outset we defined some guidelines to ensure new contributions only ever enhance the project: + +* **Quality**: Code in the Corda project should meet the [Corda coding style guidelines](https://docs.corda.net/codestyle.html), with sufficient test-cases, descriptive commit messages, evidence that the contribution does not break any compatibility commitments or cause adverse feature interactions, and evidence of high-quality peer-review. +* **Size**: The Corda project's culture is one of small pull-requests, regularly submitted. The larger a pull-request, the more likely it is that you will be asked to resubmit as a series of self-contained and individually reviewable smaller PRs. +* **Scope**: We try to ensure the Corda project remains coherent and focused so we ask that the feature's scope is within the definition specified in the Technical White Paper. +* **Maintainability**: If the feature will require ongoing maintenance (eg support for a particular brand of database), we may ask you to accept responsibility for maintaining this feature. +* **Non-duplicative**: If the contribution duplicates features that already exist or are already in progress, you may be asked to work with the project maintainers to reconcile this. As the major contributor to Corda, many employees of [R3](https://r3.com) will be working on features at any given time. To avoid surprises and foster transparency, our work tracking system, [Jira](https://r3-cev.atlassian.net/projects/CORDA/summary), is public. In addition, the maintainers and developers on the project are available on the [design](https://cordaledger.slack.com/messages/C3J04VC3V/) channel of our [Slack](https://slack.corda.net/) and they would be delighted to discuss any work you plan to do. + +**Advice to contributors**: You are encouraged to join our [Slack](https://slack.corda.net/), observe the [Pull Request](https://github.com/corda/corda/pulls) process in action, contribute to code reviews and start by submitting small changes. [This page](https://docs.corda.net/head/contributing.html) in our docsite shares tips on how to identify areas in which to contribute, and outlines the mechanics for doing so. + +To start contributing you can clone our repo and begin making pull requests. All contributions to this project are subject to the terms of the Developer Certificate of Origin, reproduced at the bottom of this page. + +## Project Leadership and Maintainers + +The leader of this project is currently [Mike Hearn](https://github.com/mikehearn), who is also the Lead Platform Engineer at R3. The project leader will appoint maintainers of the project, to whom responsibility is delegated for merging community contributions into the code base and acting as points of contact. There is currently one such community maintainer, [Joel Dudley](https://github.com/joeldudleyr3). We anticipate additional maintainers joining the project in the future from across the community. + +Transparency: In addition to the project lead and maintainer(s), developers employed by R3 who have passed our technical interview process have commit privileges to the repo. All R3 contributions undergo peer review, which is documented in public in GitHub, before they can be merged; they are held to the same standard as all other contributions. The community is encouraged both to observe and participate in this [review process](https://github.com/corda/corda/pulls). ## Community Locations -* [GitHub](https://github.com/corda/corda) -* [Forums](https://discourse.corda.net) -* [Chat](https://slack.corda.net) +The Corda maintainers, developers and extended community make active use of the [Corda Slack](http://slack.corda.net/). We also support a very active community of question askers and answerers on [Stack Overflow](https://stackoverflow.com/questions/tagged/corda). + +## Transparency and Conflict Policy + +The project is supported and maintained by the [R3 Alliance](https://www.r3.com), which consists of over two hundred firms working together to build and maintain this open source enterprise-grade blockchain platform. We develop in the open and publish our [Jira](https://r3-cev.atlassian.net/projects/CORDA/summary) to give everyone visibility. R3 also maintains and distributes a commercial distribution of Corda. Our vision is that distributions of Corda be compatible and interoperable, and our contribution and code review guidelines are designed in part to enable this. + +As the R3 Alliance is maintainer of the project and also develops a commercial distribution of Corda, what happens if a member of the community contributes a feature which the R3 team have implemented only in their commercial product? How is this apparent conflict managed? Our approach is simple: if the contribution meets the standards for the project (see above), then the existence of a competing commercial implementation will not be used as a reason to reject it. In other words, it is our policy that should a community feature be contributed which meets the criteria above, we will accept it or work with the contributor to merge/reconcile it with the commercial feature. ## Developer Certificate of Origin All contributions to this project are subject to the terms of the Developer Certificate of Origin, below: -Developer Certificate of Origin -Version 1.1 +Developer Certificate of Origin Version 1.1 -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or +(a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or +(b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. +(c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. \ No newline at end of file +(d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. From 0f15c89ee0d5b2eeb6fde81d141d8bc7d8cb9e13 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Fri, 4 May 2018 09:21:53 +0100 Subject: [PATCH 08/18] Update contributors list. (#3074) --- CONTRIBUTORS.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a715a3daf1..2074edfc0a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -37,6 +37,7 @@ see changes to this list. * Benoit Lafontaine (OCTO) * Berit Bourgonje (ING) * BitcoinErrorLog +* BMO * Bob Crozier (AIA) * Bogdan Paunescu (R3) * C-Otto @@ -49,15 +50,16 @@ see changes to this list. * Chris Akers (R3) * Chris Burlinchon (R3) * Chris Rankin (R3) -* Christian Kaufmann (Credit Suisse) +* Christian Kaufmann * Christian Sailer (R3) -* Christopher Saunders (Credit Suisse) +* Christopher Saunders * Christopher Swanson (US Bank) * Clark Thompson (R3) * Clay Ratliff (Thoughtworks) * Clemens Wan (R3) * Clinton Alexander (R3) * cncorda +* Credit Suisse * cyrsis * Daniel Roig (SEB) * Dave Hudson (R3) @@ -79,7 +81,7 @@ see changes to this list. * Ian Cusden (UBS) * Ian Grigg (R3) * Igor Nitto (R3) -* Igor Panov (CIBC) +* Igor Panov * Ivan Schasny (R3) * James Brown (R3) * James Carlyle (R3) @@ -94,7 +96,7 @@ see changes to this list. * Jose Luu (Natixis) * Josh Lindl (BCS) * Justin Chapman (Northern Trust) -* Kai-Michael Schramm (Credit Suisse) +* Kai-Michael Schramm * Karel Hajek (Barclays Capital) * karnauskas * Kasia Streich (R3) @@ -114,7 +116,7 @@ see changes to this list. * Mark Raynes (Thomson Reuters) * Mark Simpson (RBS) * Mark Tiggas (Wells Fargo) -* Massimo Morini (Banca IMI) +* Massimo Morini * Mat Rizzo (R3) * Matt Britton (BCS) * Matthew Nesbit (R3) @@ -130,7 +132,7 @@ see changes to this list. * Nuam Athaweth (MUFG) * Oscar Zibordi de Paiva (Bradesco) * Patrick Kuo (R3) -* Pekka Kaipio (OP Financial) +* Pekka Kaipio * Phillip Griffin * Piotr Piskorski (Nordea) * Przemyslaw Bak (R3) @@ -156,12 +158,11 @@ see changes to this list. * Sajindra Jayasena (Deutsche Bank) * Saket Sharma (BNY Mellon) * Sam Chadwick (Thomson Reuters) -* Sasmit Sahu (Credit Suisse) -* Scott James (Credit Suisse) +* Sasmit Sahu +* Scott James * Shams Asari (R3) * Simon Taylor (Barclays) * Sofus Mortensen (Digital Asset Holdings) -* Stephen Lane-Smith (BMO) * stevenroose * Szymon Sztuka (R3) * tb-pq @@ -169,7 +170,7 @@ see changes to this list. * Thomas O'Donnell (Macquarie) * Thomas Schroeter (R3) * Tim Swanson (R3) -* Timothy Smith (Credit Suisse) +* Timothy Smith * Tom Menner (R3) * tomconte * Tommy Lillehagen (R3) From 2cec6b095cf6181e66479c37f1467f117b29e5af Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Fri, 4 May 2018 10:21:40 +0100 Subject: [PATCH 09/18] Vault query refactor (#3065) * Move test initialization logic to jUnit rules for better composition and reusability --- .../vault/VaultQueryExceptionsTests.kt | 83 +------ .../node/services/vault/VaultQueryTests.kt | 214 +++++++++++------- 2 files changed, 144 insertions(+), 153 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryExceptionsTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryExceptionsTests.kt index ad3d5b4319..9b016ea8ec 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryExceptionsTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryExceptionsTests.kt @@ -1,6 +1,5 @@ package net.corda.node.services.vault -import net.corda.core.identity.CordaX500Name import net.corda.core.internal.packageName import net.corda.core.node.services.* import net.corda.core.node.services.vault.* @@ -9,76 +8,25 @@ import net.corda.finance.* import net.corda.finance.contracts.asset.Cash import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV3 -import net.corda.nodeapi.internal.persistence.CordaPersistence -import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.testing.core.* -import net.corda.testing.internal.rigorousMock -import net.corda.testing.internal.vault.VaultFiller -import net.corda.testing.node.MockServices -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices -import net.corda.testing.node.makeTestIdentityService import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.* import org.junit.rules.ExpectedException -class VaultQueryExceptionsTests { - private companion object { - val bankOfCorda = TestIdentity(BOC_NAME) - val cashNotary = TestIdentity(CordaX500Name("Cash Notary Service", "Zurich", "CH"), 21) - val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) - val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1) - val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) - val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) - val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) - val BOC_IDENTITY get() = bankOfCorda.identity - val BOC_KEY get() = bankOfCorda.keyPair - val CASH_NOTARY get() = cashNotary.party - val CASH_NOTARY_IDENTITY get() = cashNotary.identity - val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair - val MEGA_CORP_IDENTITY get() = megaCorp.identity - val MEGA_CORP_KEY get() = megaCorp.keyPair - val MINI_CORP_IDENTITY get() = miniCorp.identity +class VaultQueryExceptionsTests : VaultQueryParties by rule { - private val cordappPackages = listOf( - "net.corda.testing.contracts", - "net.corda.finance.contracts", - CashSchemaV1::class.packageName, - DummyLinearStateSchemaV1::class.packageName) - SampleCashSchemaV3::class.packageName - - private lateinit var services: MockServices - private lateinit var vaultFiller: VaultFiller - private lateinit var vaultFillerCashNotary: VaultFiller - private lateinit var notaryServices: MockServices - private val vaultService: VaultService get() = services.vaultService - private lateinit var identitySvc: IdentityService - private lateinit var database: CordaPersistence - - - @BeforeClass @JvmStatic - fun setUpClass() { - // register additional identities - val databaseAndServices = makeTestDatabaseAndMockServices( - cordappPackages, - makeTestIdentityService(Companion.MEGA_CORP_IDENTITY, Companion.MINI_CORP_IDENTITY, Companion.dummyCashIssuer.identity, Companion.dummyNotary.identity), - Companion.megaCorp, - moreKeys = Companion.DUMMY_NOTARY_KEY) - database = databaseAndServices.first - services = databaseAndServices.second - vaultFiller = VaultFiller(services, Companion.dummyNotary) - vaultFillerCashNotary = VaultFiller(services, Companion.dummyNotary, Companion.CASH_NOTARY) - notaryServices = MockServices(cordappPackages, Companion.dummyNotary, rigorousMock(), Companion.dummyCashIssuer.keyPair, Companion.BOC_KEY, Companion.MEGA_CORP_KEY) - identitySvc = services.identityService - // Register all of the identities we're going to use - (notaryServices.myInfo.legalIdentitiesAndCerts + Companion.BOC_IDENTITY + Companion.CASH_NOTARY_IDENTITY + Companion.MINI_CORP_IDENTITY + Companion.MEGA_CORP_IDENTITY).forEach { identity -> - services.identityService.verifyAndRegisterIdentity(identity) - } + companion object { + @ClassRule @JvmField + val rule = object : VaultQueryTestRule() { + override val cordappPackages = listOf( + "net.corda.testing.contracts", + "net.corda.finance.contracts", + CashSchemaV1::class.packageName, + DummyLinearStateSchemaV1::class.packageName) - SampleCashSchemaV3::class.packageName } } - private lateinit var transaction: DatabaseTransaction - - @Rule @JvmField val testSerialization = SerializationEnvironmentRule() @@ -87,16 +35,9 @@ class VaultQueryExceptionsTests { @JvmField val expectedEx: ExpectedException = ExpectedException.none() - @Before - fun setUp() { - transaction = database.newTransaction() - } - - @After - fun tearDown() { - transaction.rollback() - transaction.close() - } + @Rule + @JvmField + val rollbackRule = VaultQueryRollbackRule(this) @Test fun `query attempting to use unregistered schema`() { diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 6fcf1365c3..94b9929e98 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -5,6 +5,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.toStringShort import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.packageName import net.corda.core.node.services.* import net.corda.core.node.services.vault.* @@ -20,6 +21,7 @@ import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1.PersistentCashState import net.corda.finance.schemas.CommercialPaperSchemaV1 +import net.corda.finance.schemas.SampleCashSchemaV3 import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -27,16 +29,14 @@ import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.testing.core.* import net.corda.testing.internal.TEST_TX_TIME import net.corda.testing.internal.rigorousMock -import net.corda.testing.internal.vault.DUMMY_LINEAR_CONTRACT_PROGRAM_ID -import net.corda.testing.internal.vault.DummyLinearContract -import net.corda.testing.internal.vault.VaultFiller +import net.corda.testing.internal.vault.* import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.makeTestIdentityService -import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 import org.assertj.core.api.Assertions.assertThat import org.junit.* import org.junit.rules.ExpectedException +import org.junit.rules.ExternalResource import java.lang.Thread.sleep import java.time.Instant import java.time.LocalDate @@ -44,98 +44,140 @@ import java.time.ZoneOffset import java.time.temporal.ChronoUnit import java.util.* -class VaultQueryTests { - private companion object { - val alice = TestIdentity(ALICE_NAME, 70) - val bankOfCorda = TestIdentity(BOC_NAME) - val bigCorp = TestIdentity(CordaX500Name("BigCorporation", "New York", "US")) - val bob = TestIdentity(BOB_NAME, 80) - val cashNotary = TestIdentity(CordaX500Name("Cash Notary Service", "Zurich", "CH"), 21) - val charlie = TestIdentity(CHARLIE_NAME, 90) - val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) - val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1) - val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) - val DUMMY_OBLIGATION_ISSUER = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10).party - val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) - val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) - val ALICE get() = alice.party - val ALICE_IDENTITY get() = alice.identity - val BIG_CORP get() = bigCorp.party - val BIG_CORP_IDENTITY get() = bigCorp.identity - val BOB get() = bob.party - val BOB_IDENTITY get() = bob.identity - val BOC get() = bankOfCorda.party - val BOC_IDENTITY get() = bankOfCorda.identity - val BOC_KEY get() = bankOfCorda.keyPair - val BOC_PUBKEY get() = bankOfCorda.publicKey - val CASH_NOTARY get() = cashNotary.party - val CASH_NOTARY_IDENTITY get() = cashNotary.identity - val CHARLIE get() = charlie.party - val CHARLIE_IDENTITY get() = charlie.identity - val DUMMY_NOTARY get() = dummyNotary.party - val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair - val MEGA_CORP_IDENTITY get() = megaCorp.identity - val MEGA_CORP_PUBKEY get() = megaCorp.publicKey - val MEGA_CORP_KEY get() = megaCorp.keyPair - val MEGA_CORP get() = megaCorp.party - val MINI_CORP_IDENTITY get() = miniCorp.identity - val MINI_CORP get() = miniCorp.party +interface VaultQueryParties { + val alice: TestIdentity + val bankOfCorda: TestIdentity + val bigCorp: TestIdentity + val bob: TestIdentity + val cashNotary: TestIdentity + val charlie: TestIdentity + val dummyCashIssuer: TestIdentity + val DUMMY_CASH_ISSUER: PartyAndReference + val dummyNotary: TestIdentity + val DUMMY_OBLIGATION_ISSUER: Party + val megaCorp: TestIdentity + val miniCorp: TestIdentity - private val cordappPackages = listOf( - "net.corda.testing.contracts", - "net.corda.finance.contracts", - CashSchemaV1::class.packageName, - DummyLinearStateSchemaV1::class.packageName) - private lateinit var services: MockServices - private lateinit var vaultFiller: VaultFiller - private lateinit var vaultFillerCashNotary: VaultFiller - private lateinit var notaryServices: MockServices - private val vaultService: VaultService get() = services.vaultService - private lateinit var identitySvc: IdentityService - private lateinit var database: CordaPersistence + val ALICE get() = alice.party + val ALICE_IDENTITY get() = alice.identity + val BIG_CORP get() = bigCorp.party + val BIG_CORP_IDENTITY get() = bigCorp.identity + val BOB get() = bob.party + val BOB_IDENTITY get() = bob.identity + val BOC get() = bankOfCorda.party + val BOC_IDENTITY get() = bankOfCorda.identity + val BOC_KEY get() = bankOfCorda.keyPair + val BOC_PUBKEY get() = bankOfCorda.publicKey + val CASH_NOTARY get() = cashNotary.party + val CASH_NOTARY_IDENTITY get() = cashNotary.identity + val CHARLIE get() = charlie.party + val CHARLIE_IDENTITY get() = charlie.identity + val DUMMY_NOTARY get() = dummyNotary.party + val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair + val MEGA_CORP_IDENTITY get() = megaCorp.identity + val MEGA_CORP_PUBKEY get() = megaCorp.publicKey + val MEGA_CORP_KEY get() = megaCorp.keyPair + val MEGA_CORP get() = megaCorp.party + val MINI_CORP_IDENTITY get() = miniCorp.identity + val MINI_CORP get() = miniCorp.party - @BeforeClass @JvmStatic - fun setUpClass() { - // register additional identities - val databaseAndServices = makeTestDatabaseAndMockServices( - cordappPackages, - makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), - Companion.megaCorp, - moreKeys = DUMMY_NOTARY_KEY) - database = databaseAndServices.first - services = databaseAndServices.second - vaultFiller = VaultFiller(services, dummyNotary) - vaultFillerCashNotary = VaultFiller(services, dummyNotary, CASH_NOTARY) - notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY) - identitySvc = services.identityService - // Register all of the identities we're going to use - (notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity -> - services.identityService.verifyAndRegisterIdentity(identity) - } + val services: MockServices + val vaultFiller: VaultFiller + val vaultFillerCashNotary: VaultFiller + val notaryServices: MockServices + val vaultService: VaultService + val identitySvc: IdentityService + val database: CordaPersistence + + val cordappPackages: List +} + +open class VaultQueryTestRule : ExternalResource(), VaultQueryParties { + + override val alice = TestIdentity(ALICE_NAME, 70) + override val bankOfCorda = TestIdentity(BOC_NAME) + override val bigCorp = TestIdentity(CordaX500Name("BigCorporation", "New York", "US")) + override val bob = TestIdentity(BOB_NAME, 80) + override val cashNotary = TestIdentity(CordaX500Name("Cash Notary Service", "Zurich", "CH"), 21) + override val charlie = TestIdentity(CHARLIE_NAME, 90) + override val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) + override val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1) + override val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) + override val DUMMY_OBLIGATION_ISSUER = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10).party + override val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) + override val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) + override val MINI_CORP get() = miniCorp.party + + override val cordappPackages = listOf( + "net.corda.testing.contracts", + "net.corda.finance.contracts", + CashSchemaV1::class.packageName, + DummyLinearStateSchemaV1::class.packageName, + SampleCashSchemaV3::class.packageName) + + override lateinit var services: MockServices + override lateinit var vaultFiller: VaultFiller + override lateinit var vaultFillerCashNotary: VaultFiller + override lateinit var notaryServices: MockServices + override val vaultService: VaultService get() = services.vaultService + override lateinit var identitySvc: IdentityService + override lateinit var database: CordaPersistence + + + override fun before() { + // register additional identities + val databaseAndServices = makeTestDatabaseAndMockServices( + cordappPackages, + makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), + megaCorp, + moreKeys = DUMMY_NOTARY_KEY) + database = databaseAndServices.first + services = databaseAndServices.second + vaultFiller = VaultFiller(services, dummyNotary) + vaultFillerCashNotary = VaultFiller(services, dummyNotary, CASH_NOTARY) + notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY) + identitySvc = services.identityService + // Register all of the identities we're going to use + (notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity -> + services.identityService.verifyAndRegisterIdentity(identity) } } - private lateinit var transaction: DatabaseTransaction + override fun after() { + database.close() + } +} +class VaultQueryRollbackRule(val vaultQueryParties: VaultQueryParties) : ExternalResource() { - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule() + lateinit var transaction: DatabaseTransaction + + override fun before() { + transaction = vaultQueryParties.database.newTransaction() + } + + override fun after() { + transaction.rollback() + transaction.close() + } +} + +abstract class VaultQueryTestsBase : VaultQueryParties { @Rule @JvmField val expectedEx: ExpectedException = ExpectedException.none() - @Before - fun setUp() { - transaction = database.newTransaction() + @Suppress("LeakingThis") + @Rule + @JvmField + val transactionRule = VaultQueryRollbackRule(this) + + companion object { + @ClassRule @JvmField + val testSerialization = SerializationEnvironmentRule() } - @After - fun tearDown() { - transaction.rollback() - transaction.close() - } /** * Helper method for generating a Persistent H2 test database @@ -2166,4 +2208,12 @@ class VaultQueryTests { * 3) Template / Tutorial CorDapp service query extension executing Named Queries via JPA * 4) Advanced pagination queries using Spring Data (and/or Hibernate/JPQL) */ +} + +class VaultQueryTests : VaultQueryTestsBase(), VaultQueryParties by vaultQueryTestRule { + + companion object { + @ClassRule @JvmField + val vaultQueryTestRule = VaultQueryTestRule() + } } \ No newline at end of file From 24839f25e25ca350244ea17a38dea35d923cd306 Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Fri, 4 May 2018 10:22:03 +0100 Subject: [PATCH 10/18] Running Dockerform generated nodes (#3041) --- docs/source/running-a-node.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index 1628f5ab52..6f325bc1c2 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -51,8 +51,8 @@ To enable remote debugging of the node, run the following from the terminal wind This command line will start the debugger on port 5005 and pause the process awaiting debugger attachment. -Starting all nodes at once from the command line ------------------------------------------------- +Starting all nodes at once from the command line (native) +--------------------------------------------------------- If you created your nodes using ``deployNodes``, a ``runnodes`` shell script (or batch file on Windows) will have been generated to allow you to quickly start up all nodes and their webservers. ``runnodes`` should only be used for testing purposes. @@ -68,3 +68,15 @@ Start the nodes with ``runnodes`` by running the following command from the root If you receive an ``OutOfMemoryError`` exception when interacting with the nodes, you need to increase the amount of Java heap memory available to them, which you can do when running them individually. See :ref:`starting-an-individual-corda-node`. + +Starting all nodes at once from the command line (docker-compose) +----------------------------------------------------------------- +If you created your nodes using ``Dockerform``, the ``docker-compose.yml`` file and corresponding ``Dockerfile`` for +nodes has been created and configured appropriately. Navigate to ``build/nodes`` directory and run ``docker-compose up`` +command. This will startup nodes inside new, internal network. +After the nodes are started up, you can use ``docker ps`` command to see how the ports are mapped. + +.. warning:: You need both ``Docker`` and ``docker-compose`` installed and enabled to use this method. Docker CE + (Community Edition) is enough. Please refer to `Docker CE documentation `_ + and `Docker Compose documentation `_ for installation instructions for all + major operating systems. From 57b617c7d9d8ed12ac1090d9f993e751bc60527b Mon Sep 17 00:00:00 2001 From: Kat Baker Date: Fri, 4 May 2018 12:36:45 +0100 Subject: [PATCH 11/18] TEST FIX --- .../net/corda/client/rpc/ClientRPCInfrastructureTests.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt index 43cd0c1ce1..d2a0a2c977 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt @@ -1,5 +1,6 @@ package net.corda.client.rpc +import net.corda.core.CordaRuntimeException import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture @@ -11,6 +12,7 @@ import net.corda.testing.node.internal.RPCDriverDSL import net.corda.testing.node.internal.rpcDriver import net.corda.testing.node.internal.rpcTestUser import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -77,9 +79,10 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() { // Does nothing, doesn't throw. proxy.void() - assertEquals("Barf!", assertFailsWith { - proxy.barf() - }.message) + assertThatThrownBy { proxy.barf() } + .isInstanceOf(CordaRuntimeException::class.java) + .hasMessage("java.lang.IllegalArgumentException: Barf!") + assertEquals("hi 5", proxy.someCalculation("hi", 5)) } From f8a4368310e7bf8f90b23e34b7eeea720c2136bc Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Fri, 4 May 2018 13:03:02 +0100 Subject: [PATCH 12/18] Add node scheduler db tests inside use block (#3075) --- .../events/NodeSchedulerServiceTest.kt | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index db14bc1632..fa1271405a 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -234,20 +234,21 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { val timeInTheFuture = mark + 1.days val stateRef = StateRef(SecureHash.zeroHash, 0) - val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) - val scheduler = database.transaction { - createScheduler(database) - } + configureDatabase(dataSourceProps, databaseConfig, rigorousMock()).use { database -> + val scheduler = database.transaction { + createScheduler(database) + } - val ssr1 = ScheduledStateRef(stateRef, timeInTheFuture) - database.transaction { - scheduler.scheduleStateActivity(ssr1) + val ssr1 = ScheduledStateRef(stateRef, timeInTheFuture) + database.transaction { + scheduler.scheduleStateActivity(ssr1) + } + // XXX: For some reason without the commit the db closes without writing the transactions + database.dataSource.connection.commit() + + // Force the thread to shut down with operations waiting + scheduler.cancelAndWait() } - // XXX: For some reason without the commit the db closes without writing the transactions - database.dataSource.connection.commit() - // Force the thread to shut down with operations waiting - scheduler.cancelAndWait() - database.close() val flowLogic = rigorousMock>() val logicRef = rigorousMock() @@ -255,15 +256,15 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { transactionStates[stateRef] = transactionStateMock(logicRef, timeInTheFuture) flows[logicRef] = flowLogic - val newDatabase = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()) - val newScheduler = newDatabase.transaction { - createScheduler(newDatabase) - } - testClock.advanceBy(1.days) - assertStarted(flowLogic) + configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()).use { database -> + val newScheduler = database.transaction { + createScheduler(database) + } + testClock.advanceBy(1.days) + assertStarted(flowLogic) - newScheduler.join() - newDatabase.close() + newScheduler.join() + } } @Ignore("Temporarily") @@ -277,25 +278,25 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { val ssr2 = ScheduledStateRef(stateRef, timeInTheFuture) val logicRef = rigorousMock() val flowLogic = rigorousMock>() - val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) - val scheduler = database.transaction { - createScheduler(database) + configureDatabase(dataSourceProps, databaseConfig, rigorousMock()).use { database -> + val scheduler = database.transaction { + createScheduler(database) + } + + transactionStates[stateRef] = transactionStateMock(logicRef, timeInTheFuture) + flows[logicRef] = flowLogic + + database.transaction { + scheduler.scheduleStateActivity(ssr1) + session.flush() + scheduler.scheduleStateActivity(ssr2) + } + assertWaitingFor(ssr1) + testClock.advanceBy(1.days) + assertStarted(flowLogic) + + scheduler.join() } - - transactionStates[stateRef] = transactionStateMock(logicRef, timeInTheFuture) - flows[logicRef] = flowLogic - - database.transaction { - scheduler.scheduleStateActivity(ssr1) - session.flush() - scheduler.scheduleStateActivity(ssr2) - } - assertWaitingFor(ssr1) - testClock.advanceBy(1.days) - assertStarted(flowLogic) - - scheduler.join() - database.close() } } From 907031e84006a0a640e326cc341e5c86d65f0635 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Fri, 4 May 2018 14:34:45 +0100 Subject: [PATCH 13/18] [ENT-1825] Document details of cipher suites (#3073) --- docs/source/cipher-suites.rst | 102 +++++++++++++++++++++++++++ docs/source/corda-networks-index.rst | 1 + docs/source/other-index.rst | 2 +- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 docs/source/cipher-suites.rst diff --git a/docs/source/cipher-suites.rst b/docs/source/cipher-suites.rst new file mode 100644 index 0000000000..020daded67 --- /dev/null +++ b/docs/source/cipher-suites.rst @@ -0,0 +1,102 @@ +Supported cipher suites +======================= + +.. contents:: + +The set of signature schemes supported forms a part of the consensus rules for a Corda DLT network. +Thus, it is important that implementations do not support pluggability of any crypto algorithms and do take measures +to prevent algorithms supported by any underlying cryptography library from becoming accidentally accessible. +Signing a transaction with an algorithm that is not a part of the base specification would result in a transaction +being considered invalid by peer nodes and thus a loss of consensus occurring. The introduction of new algorithms +over time will require a global upgrade of all nodes. + +Corda has been designed to be cryptographically agile, in the sense that the available set of signature schemes is +carefully selected based on various factors, such as provided security-level and cryptographic strength, compatibility +with various HSM vendors, algorithm standardisation, variety of cryptographic primitives, business demand, option for +post-quantum resistance, side channel security, efficiency and rigorous testing. + +Before we present the pool of supported schemes it is useful to be familiar with :doc:`key-concepts-identity`, +:doc:`permissioning` and :doc:`api-identity`. An important design decision in Corda is its shared hierarchy +between the TLS and Node Identity certificates. + +Certificate hierarchy +--------------------- +A Corda network has 8 types of keys and a regular node requires 4 of them: + +* The **root network CA** key +* The **doorman CA** key +* The **network map** key +* The **service identity** key(s) (per service, such as a notary cluster; it can be a Composite Key) + +-- **Node Keys** -- +* The **node CA** key(s) (one per node) +* The **legal identity** key(s) (one per node) +* The **tls** key(s) (per node) +* The **confidential identity** key(s) (per node) + +We can visualise the certificate structure as follows (for a detailed description of cert-hierarchy, +see :doc:`permissioning`): + +.. image:: resources/certificate_structure.png + :scale: 55% + :align: center + +Supported cipher suites +----------------------- +Due to the shared certificate hierarchy, the following 4 key/certificate types: **root network CA**, **doorman CA**, +**node CA** and **tls** should be compatible with the standard TLS 1.2 protocol. The latter is a requirement from the +TLS certificate-path validator. It is highlighted that the rest of the keys can be any of the 5 supported cipher suites. +For instance, **network map** is ECDSA NIST P-256 (secp256r1) in the Corda Network (CN) as it is well-supported by the +underlying HSM device, but the default for dev-mode is Pure EdDSA (ed25519). + +The following table presents the 5 signature schemes currently supported by Corda. The TLS column shows which of them +are compatible with TLS 1.2, while the default scheme per key type is also shown. + ++-------------------------+-------------------------------------------------------------+-----+-----------------------+ +| Cipher suite | Description | TLS | Default for | ++=========================+=============================================================|=====+=======================+ +| Pure EdDSA using the | EdDSA represents the current state of the art in mainstream | NO | node identity | +| ed25519 curve | cryptography. It implements elliptic curve cryptography | | confidential identity | +| and SHA-512 | with deterministic signatures a fast implementation, | | network map (dev) | +| | explained constants, side channel resistance and many other | | | +| | desirable characteristics. However, it is relatively new | | | +| | and not widely supported, for example, you can't use it in | | | +| | TLS yet (a draft RFC exists but is not standardised yet). | | | ++-------------------------+-------------------------------------------------------------+-----+-----------------------+ +| ECDSA using the | This is the default choice for most systems that support | YES | root network CA | +| NIST P-256 curve | elliptic curve cryptography today and is recommended by | | doorman CA | +| (secp256r1) | NIST. It is also supported by the majority of the HSM | | node CA | +| and SHA-256 | vendors. | | tls | +| | | | network map (CN) | ++-------------------------+-------------------------------------------------------------+-----+-----------------------+ +| ECDSA using the | secp256k1 is the curve adopted by Bitcoin and as such there | YES | | +| Koblitz k1 curve | is a wealth of infrastructure, code and advanced algorithms | | | +| (secp256k1) | designed for use with it. This curve is standardised by | | | +| and SHA-256 | NIST as part of the "Suite B" cryptographic algorithms and | | | +| | as such is more widely supported than ed25519. By | | | +| | supporting it we gain access to the ecosystem of advanced | | | +| | cryptographic techniques and devices pioneered by the | | | +| | Bitcoin community. | | | ++-------------------------+-------------------------------------------------------------+-----+-----------------------+ +| RSA (3072bit) PKCS#1 | RSA is well supported by any sort of hardware or software | YES | | +| and SHA-256 | as a signature algorithm no matter how old, for example, | | | +| | legacy HSMs will support this along with obsolete operating | | | +| | systems. RSA is using bigger keys than ECDSA and thus it is | | | +| | recommended for inclusion only for its backwards | | | +| | compatibility properties, and only for usage where legacy | | | +| | constraints or government regulation forbids the usage of | | | +| | more modern approaches. | | | ++-------------------------+-------------------------------------------------------------+-----+-----------------------+ +| SPHINCS-256 | SPHINCS-256 is a post-quantum secure algorithm that relies | NO | | +| and SHA-512 | only on hash functions. It is included as a hedge against | | | +| | the possibility of a malicious adversary obtaining a | | | +| | quantum computer capable of running Shor's algorithm in | | | +| | future. SPHINCS is based ultimately on a clever usage of | | | +| | Merkle hash trees. Hash functions are a very heavily | | | +| | studied and well understood area of cryptography. Thus, it | | | +| | is assumed that there is a much lower chance of | | | +| | breakthrough attacks on the underlying mathematical | | | +| | problems. However, SPHINCS uses relatively big public keys, | | | +| | it is slower and outputs bigger signatures than EdDSA, | | | +| | ECDSA and RSA algorithms. | | | ++-------------------------+-------------------------------------------------------------+-----+-----------------------+ diff --git a/docs/source/corda-networks-index.rst b/docs/source/corda-networks-index.rst index 9315a2de09..270726948e 100644 --- a/docs/source/corda-networks-index.rst +++ b/docs/source/corda-networks-index.rst @@ -8,3 +8,4 @@ Corda networks permissioning network-map versioning + cipher-suites diff --git a/docs/source/other-index.rst b/docs/source/other-index.rst index c525b2c4cd..f6d60548e7 100644 --- a/docs/source/other-index.rst +++ b/docs/source/other-index.rst @@ -6,4 +6,4 @@ Other corda-repo-layout building-the-docs - json \ No newline at end of file + json From e564303869e778dc85f5475ca842cc35c33d2f04 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Fri, 4 May 2018 14:42:10 +0100 Subject: [PATCH 14/18] CORDA-1414: node should continue with parameters from file when network map not available (#3061) Fix CORDA-1414 If network map is not available or returns exception on network parameters reading, node should continue with parameters from file. --- .../corda/node/internal/NetworkParametersReader.kt | 5 ++--- .../network/NetworkParametersReaderTest.kt | 14 +++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt index 326c2dfd4d..fb565e149e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt @@ -10,7 +10,6 @@ import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.nodeapi.internal.network.verifiedNetworkMapCert -import java.net.ConnectException import java.nio.file.Path import java.nio.file.StandardCopyOption import java.security.cert.X509Certificate @@ -29,8 +28,8 @@ class NetworkParametersReader(private val trustRoot: X509Certificate, private fun retrieveNetworkParameters(): NetworkParameters { val advertisedParametersHash = try { networkMapClient?.getNetworkMap()?.payload?.networkParameterHash - } catch (e: ConnectException) { - logger.info("Couldn't connect to NetworkMap", e) + } catch (e: Exception) { + logger.info("Unable to download network map", e) // If NetworkMap is down while restarting the node, we should be still able to continue with parameters from file null } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt index 370657e42c..794262e6fd 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt @@ -14,6 +14,7 @@ import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.internal.network.NetworkMapServer +import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Rule @@ -27,6 +28,7 @@ class NetworkParametersReaderTest { @JvmField val testSerialization = SerializationEnvironmentRule(true) + val fs = Jimfs.newFileSystem(Configuration.unix()) private val cacheTimeout = 100000.seconds private lateinit var server: NetworkMapServer @@ -42,11 +44,11 @@ class NetworkParametersReaderTest { @After fun tearDown() { server.close() + fs.close() } @Test fun `read correct set of parameters from file`() { - val fs = Jimfs.newFileSystem(Configuration.unix()) val baseDirectory = fs.getPath("/node").createDirectories() val oldParameters = testNetworkParameters(epoch = 1) NetworkParametersCopier(oldParameters).install(baseDirectory) @@ -60,4 +62,14 @@ class NetworkParametersReaderTest { .verifiedNetworkMapCert(DEV_ROOT_CA.certificate) assertEquals(server.networkParameters, parametersFromFile) } + + @Test + fun `read network parameters from file when network map server is down`() { + server.close() + val baseDirectory = fs.getPath("/node").createDirectories() + val fileParameters = testNetworkParameters(epoch = 1) + NetworkParametersCopier(fileParameters).install(baseDirectory) + val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, baseDirectory).networkParameters + assertThat(parameters).isEqualTo(fileParameters) + } } \ No newline at end of file From 100f680042a06fffaf6eedda4facc24001657fed Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Fri, 4 May 2018 15:04:52 +0100 Subject: [PATCH 15/18] CORDA-1001 - Remove peristent map in NodeSchedulerService (#763) (#3076) * CORDA-1001 - Remove peristent map in NodeSchedulerService (#763) * Add scheduled flow test that uses multithreaded node * Replace use of PersistentMap in NodeSchedulerService * Correct class name and remove duplicate test * Address initial PR comments * Remove debugging code * Remove acidentally added line * Move Scheduled State contracts to internal module * Put things in the right places * Add changelog message * Fix countdown issue * Addressing PR comments * Remove unused class --- docs/source/changelog.rst | 3 + .../events/ScheduledFlowIntegrationTests.kt | 126 ++++++++++++++++++ .../net/corda/testMessage/ScheduledState.kt | 75 +++++++++++ .../services/events/NodeSchedulerService.kt | 90 ++++--------- .../PersistentScheduledFlowRepository.kt | 68 ++++++++++ .../events/NodeSchedulerServiceTest.kt | 55 +++++++- .../PersistentScheduledFlowRepositoryTest.kt | 69 ++++++++++ 7 files changed, 420 insertions(+), 66 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt create mode 100644 node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 5d0f410e30..f1dc7ffd0f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -36,6 +36,9 @@ Unreleased * java.security.cert.X509CRL serialization support added. +* Replaced the ``PersistentMap`` in ``NodeSchedulerService`` with an implementation that only loads the next scheduled + state from the database into memory, rather than them all. + * Upgraded H2 to v1.4.197. * Shell (embedded available only in dev mode or via SSH) connects to the node via RPC instead of using the ``CordaRPCOps`` object directly. diff --git a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt new file mode 100644 index 0000000000..417da3155a --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt @@ -0,0 +1,126 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package net.corda.node.services.events + +import co.paralleluniverse.fibers.Suspendable +import com.google.common.collect.ImmutableList +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.concurrent.CordaFuture +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.transpose +import net.corda.core.messaging.startFlow +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds +import net.corda.testMessage.ScheduledState +import net.corda.testMessage.SpentState +import net.corda.testing.contracts.DummyContract +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.dummyCommand +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.node.User +import org.junit.Test +import java.time.Instant +import java.util.* +import kotlin.test.assertEquals + +class ScheduledFlowIntegrationTests { + @StartableByRPC + class InsertInitialStateFlow(private val destination: Party, private val notary: Party, private val identity: Int = 1, private val scheduledFor: Instant? = null) : FlowLogic() { + @Suspendable + override fun call() { + val scheduledState = ScheduledState(scheduledFor + ?: serviceHub.clock.instant(), ourIdentity, destination, identity.toString()) + val builder = TransactionBuilder(notary) + .addOutputState(scheduledState, DummyContract.PROGRAM_ID) + .addCommand(dummyCommand(ourIdentity.owningKey)) + val tx = serviceHub.signInitialTransaction(builder) + subFlow(FinalityFlow(tx)) + } + } + + @StartableByRPC + class AnotherFlow(private val identity: String) : FlowLogic() { + @Suspendable + override fun call() { + val results = serviceHub.vaultService.queryBy(QueryCriteria.LinearStateQueryCriteria(externalId = ImmutableList.of(identity))) + val state = results.states.firstOrNull() ?: return + require(!state.state.data.processed) { "Cannot spend an already processed state" } + val lock = UUID.randomUUID() + serviceHub.vaultService.softLockReserve(lock, NonEmptySet.of(state.ref)) + val notary = state.state.notary + val outputState = SpentState(identity, ourIdentity, state.state.data.destination) + val builder = TransactionBuilder(notary) + .addInputState(state) + .addOutputState(outputState, DummyContract.PROGRAM_ID) + .addCommand(dummyCommand(ourIdentity.owningKey)) + val tx = serviceHub.signInitialTransaction(builder) + subFlow(FinalityFlow(tx, outputState.participants.toSet())) + } + } + + private fun MutableList>.getOrThrowAll() { + forEach { + try { + it.getOrThrow() + } catch (ex: Exception) { + } + } + } + + @Test + fun `test that when states are being spent at the same time that schedules trigger everything is processed`() { + driver(DriverParameters( + startNodesInProcess = true, + extraCordappPackagesToScan = listOf("net.corda.testing.contracts", "net.corda.testMessage") + )) { + val N = 23 + val rpcUser = User("admin", "admin", setOf("ALL")) + val (alice, bob) = listOf(ALICE_NAME, BOB_NAME).map { startNode(providedName = it, rpcUsers = listOf(rpcUser)) }.transpose().getOrThrow() + + val aliceClient = CordaRPCClient(alice.rpcAddress).start(rpcUser.username, rpcUser.password) + val bobClient = CordaRPCClient(bob.rpcAddress).start(rpcUser.username, rpcUser.password) + + val scheduledFor = Instant.now().plusSeconds(20) + val initialiseFutures = mutableListOf>() + for (i in 0 until N) { + initialiseFutures.add(aliceClient.proxy.startFlow(::InsertInitialStateFlow, bob.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i, scheduledFor).returnValue) + initialiseFutures.add(bobClient.proxy.startFlow(::InsertInitialStateFlow, alice.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i + 100, scheduledFor).returnValue) + } + initialiseFutures.getOrThrowAll() + + val spendAttemptFutures = mutableListOf>() + for (i in (0 until N).reversed()) { + spendAttemptFutures.add(aliceClient.proxy.startFlow(::AnotherFlow, (i).toString()).returnValue) + spendAttemptFutures.add(bobClient.proxy.startFlow(::AnotherFlow, (i + 100).toString()).returnValue) + } + spendAttemptFutures.getOrThrowAll() + + val aliceStates = aliceClient.proxy.vaultQuery(ScheduledState::class.java).states.filter { it.state.data.processed } + val aliceSpentStates = aliceClient.proxy.vaultQuery(SpentState::class.java).states + + val bobStates = bobClient.proxy.vaultQuery(ScheduledState::class.java).states.filter { it.state.data.processed } + val bobSpentStates = bobClient.proxy.vaultQuery(SpentState::class.java).states + + assertEquals(aliceStates.count() + aliceSpentStates.count(), N * 2) + assertEquals(bobStates.count() + bobSpentStates.count(), N * 2) + assertEquals(aliceSpentStates.count(), bobSpentStates.count()) + } + } +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt b/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt new file mode 100644 index 0000000000..cc5f14e8ee --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt @@ -0,0 +1,75 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package net.corda.testMessage + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.* +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowLogicRefFactory +import net.corda.core.flows.SchedulableFlow +import net.corda.core.identity.Party +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.NonEmptySet +import net.corda.testing.contracts.DummyContract +import net.corda.testing.core.dummyCommand +import java.time.Instant +import java.util.* +import kotlin.reflect.jvm.jvmName + +@SchedulableFlow +class ScheduledFlow(private val stateRef: StateRef) : FlowLogic() { + @Suspendable + override fun call() { + val state = serviceHub.toStateAndRef(stateRef) + val scheduledState = state.state.data + // Only run flow over states originating on this node + if (!serviceHub.myInfo.isLegalIdentity(scheduledState.source)) { + return + } + require(!scheduledState.processed) { "State should not have been previously processed" } + val lock = UUID.randomUUID() + serviceHub.vaultService.softLockReserve(lock, NonEmptySet.of(state.ref)) + val notary = state.state.notary + val newStateOutput = scheduledState.copy(processed = true) + val builder = TransactionBuilder(notary) + .addInputState(state) + .addOutputState(newStateOutput, DummyContract.PROGRAM_ID) + .addCommand(dummyCommand(ourIdentity.owningKey)) + val tx = serviceHub.signInitialTransaction(builder) + subFlow(FinalityFlow(tx, setOf(scheduledState.destination))) + } +} + +data class ScheduledState(val creationTime: Instant, + val source: Party, + val destination: Party, + val identity: String, + val processed: Boolean = false, + val scheduledFor: Instant = creationTime, + override val linearId: UniqueIdentifier = UniqueIdentifier(externalId = identity)) : SchedulableState, LinearState { + override val participants get() = listOf(source, destination) + override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? { + return if (!processed) { + val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.jvmName, thisStateRef) + ScheduledActivity(logicRef, scheduledFor) + } else { + null + } + } +} + +data class SpentState(val identity: String, + val source: Party, + val destination: Party, + override val linearId: UniqueIdentifier = UniqueIdentifier(externalId = identity)) : LinearState { + override val participants: List get() = listOf(source, destination) +} diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index bfada7354d..22de33c70e 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -8,7 +8,6 @@ import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateRef -import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.internal.ThreadBox @@ -27,7 +26,6 @@ import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.NodePropertiesStore import net.corda.node.services.api.SchedulerService import net.corda.node.services.messaging.DeduplicationHandler -import net.corda.node.utilities.PersistentMap import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.activemq.artemis.utils.ReusableLatch @@ -36,7 +34,6 @@ import org.slf4j.Logger import java.io.Serializable import java.time.Duration import java.time.Instant -import java.util.* import java.util.concurrent.* import javax.annotation.concurrent.ThreadSafe import javax.persistence.Column @@ -67,11 +64,12 @@ class NodeSchedulerService(private val clock: CordaClock, private val nodeProperties: NodePropertiesStore, private val drainingModePollPeriod: Duration, private val log: Logger = staticLog, - private val scheduledStates: MutableMap = createMap()) + private val schedulerRepo: ScheduledFlowRepository = PersistentScheduledFlowRepository(database)) : SchedulerService, SingletonSerializeAsToken() { companion object { private val staticLog get() = contextLogger() + /** * Wait until the given [Future] is complete or the deadline is reached, with support for [MutableClock] implementations * used in demos or testing. This will substitute a Fiber compatible Future so the current @@ -110,26 +108,6 @@ class NodeSchedulerService(private val clock: CordaClock, return future.isDone } - fun createMap(): PersistentMap { - return PersistentMap( - toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, - fromPersistentEntity = { - //TODO null check will become obsolete after making DB/JPA columns not nullable - val txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") - val index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") - Pair(StateRef(SecureHash.parse(txId), index), - ScheduledStateRef(StateRef(SecureHash.parse(txId), index), it.scheduledAt)) - }, - toPersistentEntity = { key: StateRef, value: ScheduledStateRef -> - PersistentScheduledState().apply { - output = PersistentStateRef(key.txhash.toString(), key.index) - scheduledAt = value.scheduledAt - } - }, - persistentEntityClass = PersistentScheduledState::class.java - ) - } - /** * Convert a Guava [ListenableFuture] or JDK8 [CompletableFuture] to Quasar implementation and set to true when a result * or [Throwable] is available in the original. @@ -160,9 +138,8 @@ class NodeSchedulerService(private val clock: CordaClock, ) : Serializable private class InnerState { - var scheduledStatesQueue: PriorityQueue = PriorityQueue({ a, b -> a.scheduledAt.compareTo(b.scheduledAt) }) - var rescheduled: GuavaSettableFuture? = null + var nextScheduledAction: ScheduledStateRef? = null } // Used to de-duplicate flow starts in case a flow is starting but the corresponding entry hasn't been removed yet @@ -173,27 +150,21 @@ class NodeSchedulerService(private val clock: CordaClock, // We need the [StateMachineManager] to be constructed before this is called in case it schedules a flow. fun start() { mutex.locked { - scheduledStatesQueue.addAll(scheduledStates.values) rescheduleWakeUp() } } override fun scheduleStateActivity(action: ScheduledStateRef) { log.trace { "Schedule $action" } - val previousState = scheduledStates[action.ref] - scheduledStates[action.ref] = action + if (!schedulerRepo.merge(action)) { + // Only increase the number of unfinished schedules if the state didn't already exist on the queue + unfinishedSchedules.countUp() + } mutex.locked { - val previousEarliest = scheduledStatesQueue.peek() - scheduledStatesQueue.remove(previousState) - scheduledStatesQueue.add(action) - if (previousState == null && action !in startingStateRefs) { - unfinishedSchedules.countUp() - } - - if (action.scheduledAt.isBefore(previousEarliest?.scheduledAt ?: Instant.MAX)) { + if (action.scheduledAt < nextScheduledAction?.scheduledAt ?: Instant.MAX) { // We are earliest rescheduleWakeUp() - } else if (previousEarliest?.ref == action.ref && previousEarliest.scheduledAt != action.scheduledAt) { + } else if (action.ref == nextScheduledAction?.ref && action.scheduledAt != nextScheduledAction?.scheduledAt) { // We were earliest but might not be any more rescheduleWakeUp() } @@ -202,17 +173,12 @@ class NodeSchedulerService(private val clock: CordaClock, override fun unscheduleStateActivity(ref: StateRef) { log.trace { "Unschedule $ref" } - val removedAction = scheduledStates.remove(ref) + if (startingStateRefs.all { it.ref != ref } && schedulerRepo.delete(ref)) { + unfinishedSchedules.countDown() + } mutex.locked { - if (removedAction != null) { - val wasNext = (removedAction == scheduledStatesQueue.peek()) - val wasRemoved = scheduledStatesQueue.remove(removedAction) - if (wasRemoved) { - unfinishedSchedules.countDown() - } - if (wasNext) { - rescheduleWakeUp() - } + if (nextScheduledAction?.ref == ref) { + rescheduleWakeUp() } } } @@ -231,7 +197,9 @@ class NodeSchedulerService(private val clock: CordaClock, val (scheduledState, ourRescheduledFuture) = mutex.alreadyLocked { rescheduled?.cancel(false) rescheduled = GuavaSettableFuture.create() - Pair(scheduledStatesQueue.peek(), rescheduled!!) + //get the next scheduled action that isn't currently running + nextScheduledAction = schedulerRepo.getLatest(startingStateRefs.size + 1).firstOrNull { !startingStateRefs.contains(it.second) }?.second + Pair(nextScheduledAction, rescheduled!!) } if (scheduledState != null) { schedulerTimerExecutor.execute { @@ -261,7 +229,7 @@ class NodeSchedulerService(private val clock: CordaClock, private inner class FlowStartDeduplicationHandler(val scheduledState: ScheduledStateRef) : DeduplicationHandler { override fun insideDatabaseTransaction() { - scheduledStates.remove(scheduledState.ref) + schedulerRepo.delete(scheduledState.ref) } override fun afterDatabaseTransaction() { @@ -276,12 +244,9 @@ class NodeSchedulerService(private val clock: CordaClock, private fun onTimeReached(scheduledState: ScheduledStateRef) { var flowName: String? = "(unknown)" try { - // We need to check this before the database transaction, otherwise there is a subtle race between a - // doubly-reached deadline and the removal from [startingStateRefs]. - if (scheduledState !in startingStateRefs) { - val scheduledFlow = database.transaction { getScheduledFlow(scheduledState) } + database.transaction { + val scheduledFlow = getFlow(scheduledState) if (scheduledFlow != null) { - startingStateRefs.add(scheduledState) flowName = scheduledFlow.javaClass.name // TODO refactor the scheduler to store and propagate the original invocation context val context = InvocationContext.newInstance(InvocationOrigin.Scheduled(scheduledState)) @@ -297,24 +262,20 @@ class NodeSchedulerService(private val clock: CordaClock, } } - private fun getScheduledFlow(scheduledState: ScheduledStateRef): FlowLogic<*>? { + private fun getFlow(scheduledState: ScheduledStateRef): FlowLogic<*>? { val scheduledActivity = getScheduledActivity(scheduledState) var scheduledFlow: FlowLogic<*>? = null mutex.locked { // need to remove us from those scheduled, but only if we are still next - val previousState = scheduledStates[scheduledState.ref] - if (previousState != null && previousState === scheduledState) { + if (nextScheduledAction != null && nextScheduledAction === scheduledState) { if (scheduledActivity == null) { log.info("Scheduled state $scheduledState has rescheduled to never.") unfinishedSchedules.countDown() - scheduledStates.remove(scheduledState.ref) - scheduledStatesQueue.remove(scheduledState) + schedulerRepo.delete(scheduledState.ref) } else if (scheduledActivity.scheduledAt.isAfter(clock.instant())) { log.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.") val newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) - scheduledStates[scheduledState.ref] = newState - scheduledStatesQueue.remove(scheduledState) - scheduledStatesQueue.add(newState) + schedulerRepo.merge(newState) } else { val flowLogic = flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef) scheduledFlow = when { @@ -325,7 +286,8 @@ class NodeSchedulerService(private val clock: CordaClock, } else -> { log.trace { "Scheduler starting FlowLogic $flowLogic" } - scheduledStatesQueue.remove(scheduledState) + //Add this to the in memory list of starting refs so it is not picked up on the next rescheduleWakeUp() + startingStateRefs.add(scheduledState) flowLogic } } diff --git a/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt b/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt new file mode 100644 index 0000000000..21a796d841 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt @@ -0,0 +1,68 @@ +package net.corda.node.services.events + +import net.corda.core.contracts.ScheduledStateRef +import net.corda.core.contracts.StateRef +import net.corda.core.crypto.SecureHash +import net.corda.core.schemas.PersistentStateRef +import net.corda.nodeapi.internal.persistence.CordaPersistence + +interface ScheduledFlowRepository { + fun delete(key: StateRef): Boolean + fun merge(value: ScheduledStateRef): Boolean + fun getLatest(lookahead: Int) : List> +} + +class PersistentScheduledFlowRepository(val database: CordaPersistence): ScheduledFlowRepository { + private fun toPersistentEntityKey(stateRef: StateRef): PersistentStateRef { + return PersistentStateRef(stateRef.txhash.toString(), stateRef.index) + } + + private fun toPersistentEntity(key: StateRef, value: ScheduledStateRef): NodeSchedulerService.PersistentScheduledState { + return NodeSchedulerService.PersistentScheduledState().apply { + output = PersistentStateRef(key.txhash.toString(), key.index) + scheduledAt = value.scheduledAt + } + } + + private fun fromPersistentEntity(scheduledStateRecord: NodeSchedulerService.PersistentScheduledState): Pair { + val txId = scheduledStateRecord.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") + val index = scheduledStateRecord.output.index ?: throw IllegalStateException("DB returned null integer index") + return Pair(StateRef(SecureHash.parse(txId), index), ScheduledStateRef(StateRef(SecureHash.parse(txId), index), scheduledStateRecord.scheduledAt)) + } + + override fun delete(key: StateRef): Boolean { + return database.transaction { + val elem = session.find(NodeSchedulerService.PersistentScheduledState::class.java, toPersistentEntityKey(key!!)) + if (elem != null) { + session.remove(elem) + true + } else { + false + } + } + } + + override fun merge(value: ScheduledStateRef): Boolean { + return database.transaction { + val existingEntry = session.find(NodeSchedulerService.PersistentScheduledState::class.java, toPersistentEntityKey(value.ref)) + if (existingEntry != null) { + session.merge(toPersistentEntity(value.ref, value)) + true + } else { + session.save(toPersistentEntity(value.ref, value)) + false + } + } + } + + override fun getLatest(lookahead: Int) : List> { + return database.transaction { + val criteriaQuery = session.criteriaBuilder.createQuery(NodeSchedulerService.PersistentScheduledState::class.java) + val shed = criteriaQuery.from(NodeSchedulerService.PersistentScheduledState::class.java) + criteriaQuery.select(shed) + criteriaQuery.orderBy(session.criteriaBuilder.asc(shed.get("scheduledAt"))) + session.createQuery(criteriaQuery).setFirstResult(0).setMaxResults(lookahead) + .resultList.map { e -> fromPersistentEntity(e as NodeSchedulerService.PersistentScheduledState) } + } + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index fa1271405a..39ca9a3727 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -14,6 +14,7 @@ import net.corda.core.utilities.days import net.corda.node.internal.configureDatabase import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.NodePropertiesStore +import net.corda.node.services.messaging.DeduplicationHandler import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseTransaction @@ -30,6 +31,7 @@ import org.slf4j.Logger import java.time.Clock import java.time.Duration import java.time.Instant +import kotlin.test.assertEquals open class NodeSchedulerServiceTestBase { protected class Event(time: Instant) { @@ -48,8 +50,14 @@ open class NodeSchedulerServiceTestBase { rigorousMock().block() }.whenever(it).transaction(any()) } + protected val flowStarter = rigorousMock().also { - doReturn(openFuture>()).whenever(it).startFlow(any>(), any(), any()) + doAnswer { + val dedupe = it.arguments[2] as DeduplicationHandler + dedupe.insideDatabaseTransaction() + dedupe.afterDatabaseTransaction() + openFuture>() + }.whenever(it).startFlow(any>(), any(), any()) } private val flowsDraingMode = rigorousMock().also { doReturn(false).whenever(it).isEnabled() @@ -88,6 +96,31 @@ open class NodeSchedulerServiceTestBase { protected fun assertStarted(event: Event) = assertStarted(event.flowLogic) } +class MockScheduledFlowRepository : ScheduledFlowRepository { + private val map = HashMap() + + override fun getLatest(lookahead: Int): List> { + return map.values.sortedBy { it.scheduledAt }.map { Pair(it.ref, it) } + } + + override fun merge(value: ScheduledStateRef): Boolean { + var result = false + if (map.containsKey(value.ref)) { + result = true + } + map.put(value.ref, value) + return result + } + + override fun delete(key: StateRef): Boolean { + if (map.containsKey(key)) { + map.remove(key) + return true + } + return false + } +} + class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { private val database = rigorousMock().also { doAnswer { @@ -105,7 +138,9 @@ class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { nodeProperties = nodeProperties, drainingModePollPeriod = Duration.ofSeconds(5), log = log, - scheduledStates = mutableMapOf()).apply { start() } + schedulerRepo = MockScheduledFlowRepository() + ).apply { start() } + @Rule @JvmField val tearDown = object : TestWatcher() { @@ -228,6 +263,22 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { } } + @Test + fun `test that correct item is returned`() { + val dataSourceProps = MockServices.makeTestDataSourceProperties() + val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) + database.transaction { + val repo = PersistentScheduledFlowRepository(database) + val stateRef = StateRef(SecureHash.randomSHA256(), 0) + val ssr = ScheduledStateRef(stateRef, mark) + repo.merge(ssr) + + val output = repo.getLatest(5).firstOrNull() + assertEquals(output?.first, stateRef) + assertEquals(output?.second, ssr) + } + } + @Test fun `test that schedule is persisted`() { val dataSourceProps = MockServices.makeTestDataSourceProperties() diff --git a/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt b/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt new file mode 100644 index 0000000000..d6421757fd --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt @@ -0,0 +1,69 @@ +package net.corda.node.services.events + +import net.corda.core.contracts.ScheduledStateRef +import net.corda.core.contracts.StateRef +import net.corda.core.crypto.SecureHash +import net.corda.core.utilities.days +import net.corda.node.internal.configureDatabase +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.MockServices +import org.junit.Test +import java.time.Instant +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class PersistentScheduledFlowRepositoryTest { + private val databaseConfig: DatabaseConfig = DatabaseConfig() + private val mark = Instant.now() + + @Test + fun `test that earliest item is returned`() { + val laterTime = mark + 1.days + val dataSourceProps = MockServices.makeTestDataSourceProperties() + val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) + + database.transaction { + val repo = PersistentScheduledFlowRepository(database) + val laterStateRef = StateRef(SecureHash.randomSHA256(), 0) + val laterSsr = ScheduledStateRef(laterStateRef, laterTime) + repo.merge(laterSsr) + + val earlierStateRef = StateRef(SecureHash.randomSHA256(), 0) + val earlierSsr = ScheduledStateRef(earlierStateRef, mark) + repo.merge(earlierSsr) + + val output = repo.getLatest(5).firstOrNull() + assertEquals(output?.first, earlierStateRef) + assertEquals(output?.second, earlierSsr) + } + } + + @Test + fun `test that item is rescheduled`() { + val laterTime = mark + 1.days + val dataSourceProps = MockServices.makeTestDataSourceProperties() + val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) + database.transaction { + val repo = PersistentScheduledFlowRepository(database) + val stateRef = StateRef(SecureHash.randomSHA256(), 0) + val laterSsr = ScheduledStateRef(stateRef, laterTime) + + repo.merge(laterSsr) + + //Update the existing scheduled flow to an earlier time + val updatedEarlierSsr = ScheduledStateRef(stateRef, mark) + repo.merge(updatedEarlierSsr) + + val output = repo.getLatest(5).firstOrNull() + assertEquals(output?.first, stateRef) + assertEquals(output?.second, updatedEarlierSsr) + + repo.delete(output?.first!!) + + //There should be no more outputs + val nextOutput = repo.getLatest(5).firstOrNull() + assertNull(nextOutput) + } + } +} \ No newline at end of file From 327f3383f9724eafe865debb60b2265961c14ec3 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 4 May 2018 16:14:23 +0100 Subject: [PATCH 16/18] Revert "TEST FIX" This reverts commit 57b617c7d9d8ed12ac1090d9f993e751bc60527b. --- .../net/corda/client/rpc/ClientRPCInfrastructureTests.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt index d2a0a2c977..43cd0c1ce1 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt @@ -1,6 +1,5 @@ package net.corda.client.rpc -import net.corda.core.CordaRuntimeException import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture @@ -12,7 +11,6 @@ import net.corda.testing.node.internal.RPCDriverDSL import net.corda.testing.node.internal.rpcDriver import net.corda.testing.node.internal.rpcTestUser import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -79,10 +77,9 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() { // Does nothing, doesn't throw. proxy.void() - assertThatThrownBy { proxy.barf() } - .isInstanceOf(CordaRuntimeException::class.java) - .hasMessage("java.lang.IllegalArgumentException: Barf!") - + assertEquals("Barf!", assertFailsWith { + proxy.barf() + }.message) assertEquals("hi 5", proxy.someCalculation("hi", 5)) } From b5e8dc5bd189f7ded134bace11a7e933093c2267 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Fri, 4 May 2018 22:37:56 +0700 Subject: [PATCH 17/18] [CORDA-1338]: Error with VaultQuery for entity inheriting from CommonSchemaV1.FungibleState - FIX (#3025) --- .../node/services/vault/QueryCriteriaUtils.kt | 163 +++++++++++++++--- docs/source/changelog.rst | 2 + .../contracts/asset/DummyFungibleContract.kt | 10 +- .../finance/schemas/SampleCashSchemaV2.kt | 37 ++-- .../schemas/SampleCommercialPaperSchemaV2.kt | 36 ++-- .../vault/HibernateQueryCriteriaParser.kt | 2 +- .../services/vault/VaultQueryJavaTests.java | 85 +++++---- .../node/services/vault/VaultQueryTests.kt | 39 ++++- 8 files changed, 266 insertions(+), 108 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 3824a1d55e..b104eb58cc 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -7,6 +7,8 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import java.lang.reflect.Field +import kotlin.jvm.internal.CallableReference +import kotlin.reflect.KClass import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter @@ -67,8 +69,21 @@ sealed class CriteriaExpression { @CordaSerializable class Column(val name: String, val declaringClass: Class<*>) { + @Deprecated("Does not support fields from a MappedSuperclass. Use the equivalent that accepts a FieldInfo.") constructor(field: Field) : this(field.name, field.declaringClass) - constructor(property: KProperty1) : this(property.name, property.javaGetter!!.declaringClass) + constructor(field: FieldInfo) : this(field.name, field.entityClass) + constructor(property: KProperty1) : this(property.name, declaringClass(property)) + + private companion object { + fun declaringClass(property: KProperty1): Class<*> { + return when (property) { + // This is to ensure that, for a JPA Entity, a field declared in a MappedSuperclass will not cause Hibernate to reject a query referencing it. + // TODO remove the cast and access the owner properly after it will be exposed as Kotlin's public API (https://youtrack.jetbrains.com/issue/KT-24170). + is CallableReference -> ((property as CallableReference).owner as KClass<*>).javaObjectType + else -> property.javaGetter!!.declaringClass + } + } + } } @CordaSerializable @@ -217,16 +232,23 @@ object Builder { fun > compare(operator: BinaryComparisonOperator, value: R) = ColumnPredicate.BinaryComparison(operator, value) fun KProperty1.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) - fun Field.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.predicate(predicate: ColumnPredicate) = info().predicate(predicate) + fun FieldInfo.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) fun KProperty1.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy) - fun Field.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = info().functionPredicate(predicate, groupByColumns, orderBy) + + fun FieldInfo.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy) fun > KProperty1.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) - fun > Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = info().comparePredicate(operator, value) + fun > FieldInfo.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) fun KProperty1.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)) fun KProperty1.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)) @@ -239,31 +261,57 @@ object Builder { fun > KProperty1.notIn(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)) @JvmStatic - fun Field.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.equal(value: R) = info().equal(value) + @JvmStatic + fun FieldInfo.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)) @JvmStatic - fun Field.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.notEqual(value: R) = info().notEqual(value) + @JvmStatic + fun FieldInfo.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)) @JvmStatic - fun > Field.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.lessThan(value: R) = info().lessThan(value) + @JvmStatic + fun > FieldInfo.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value) @JvmStatic - fun > Field.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.lessThanOrEqual(value: R) = info().lessThanOrEqual(value) + @JvmStatic + fun > FieldInfo.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value) @JvmStatic - fun > Field.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.greaterThan(value: R) = info().greaterThan(value) + @JvmStatic + fun > FieldInfo.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value) @JvmStatic - fun > Field.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.greaterThanOrEqual(value: R) = info().greaterThanOrEqual(value) + @JvmStatic + fun > FieldInfo.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value) @JvmStatic - fun > Field.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.between(from: R, to: R) = info().between(from, to) + @JvmStatic + fun > FieldInfo.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to)) @JvmStatic - fun > Field.`in`(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.`in`(collection: Collection) = info().`in`(collection) + fun > FieldInfo.`in`(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)) @JvmStatic - fun > Field.notIn(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.notIn(collection: Collection) = info().notIn(collection) + @JvmStatic + fun > FieldInfo.notIn(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)) fun equal(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value) fun notEqual(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value) @@ -282,19 +330,31 @@ object Builder { fun KProperty1.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string)) @JvmStatic - fun Field.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.like(string: String) = info().like(string) + @JvmStatic + fun FieldInfo.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string)) fun KProperty1.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)) @JvmStatic - fun Field.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.notLike(string: String) = info().notLike(string) + @JvmStatic + fun FieldInfo.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)) fun KProperty1.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL)) @JvmStatic - fun Field.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.isNull() = info().isNull() + @JvmStatic + fun FieldInfo.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL)) fun KProperty1.notNull() = predicate(ColumnPredicate.NullExpression(NullOperator.NOT_NULL)) @JvmStatic - fun Field.notNull() = predicate(ColumnPredicate.NullExpression(NullOperator.NOT_NULL)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.notNull() = info().notNull() + @JvmStatic + fun FieldInfo.notNull() = predicate(ColumnPredicate.NullExpression(NullOperator.NOT_NULL)) /** aggregate functions */ fun KProperty1.sum(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = @@ -302,19 +362,31 @@ object Builder { @JvmStatic @JvmOverloads - fun Field.sum(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.sum(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = info().sum(groupByColumns?.map { it.info() }, orderBy) + @JvmStatic + @JvmOverloads + fun FieldInfo.sum(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) @JvmStatic - fun Field.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.count() = info().count() + @JvmStatic + fun FieldInfo.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) fun KProperty1.avg(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads - fun Field.avg(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.avg(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = info().avg(groupByColumns?.map { it.info() }, orderBy) + + @JvmStatic + @JvmOverloads + fun FieldInfo.avg(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.min(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = @@ -322,7 +394,12 @@ object Builder { @JvmStatic @JvmOverloads - fun Field.min(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.min(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = info().min(groupByColumns?.map { it.info() }, orderBy) + + @JvmStatic + @JvmOverloads + fun FieldInfo.min(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.max(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = @@ -330,8 +407,50 @@ object Builder { @JvmStatic @JvmOverloads - fun Field.max(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.max(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = info().max(groupByColumns?.map { it.info() }, orderBy) + + @JvmStatic + @JvmOverloads + fun FieldInfo.max(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column(it) }, orderBy) + + private fun Field.info(): FieldInfo = FieldInfo(name, declaringClass) } inline fun builder(block: Builder.() -> A) = block(Builder) + +/** + * Contains information about a field from an entity class. + * Used as part of query criteria construction through [Builder], produced by function [getField]. + * The constructor should not be invoked manually. + * + * @param name field name + * @param entityClass JPA entity class for the query + */ +class FieldInfo internal constructor(val name: String, val entityClass: Class<*>) + +/** + * Returns a [FieldInfo] for field with name [fieldName] in [entityClass]. + * + * @param fieldName name of the field + * @param entityClass JPA entity class containing the field + * @throws NoSuchFieldException if no field with name [fieldName] is found in the class hierarchy of [entityClass] + */ +@Throws(NoSuchFieldException::class) +fun getField(fieldName: String, entityClass: Class<*>): FieldInfo { + return getField(fieldName, entityClass, entityClass) +} + +@Throws(NoSuchFieldException::class) +private fun getField(fieldName: String, clazz: Class<*>?, invokingClazz: Class<*>): FieldInfo { + if (clazz == null) { + throw NoSuchFieldException(fieldName) + } + return try { + val field = clazz.getDeclaredField(fieldName) + return FieldInfo(field.name, invokingClazz) + } catch (e: NoSuchFieldException) { + getField(fieldName, clazz.superclass, invokingClazz) + } +} \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f1dc7ffd0f..d99a254fff 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -12,6 +12,8 @@ Unreleased * Refactor RPC Server Kryo observable serializer into it's own sub module +* The Vault Criteria API has been extended to take a more precise specification of which class contains a field. This primarily impacts Java users; Kotlin users need take no action. The old methods have been deprecated but still work - the new methods avoid bugs that can occur when JPA schemas inherit from each other. + * Refactor RPC Client Kryo observable serializer into it's own sub module * Fix CORDA-1403 where a property of a class that implemented a generic interface could not be deserialized in diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt index 62f3b7ad8f..e3eb4f2779 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt @@ -50,12 +50,12 @@ class DummyFungibleContract : OnLedgerAsset SampleCashSchemaV2.PersistentCashState( - _participants = this.participants.toMutableSet(), - _owner = this.owner, - _quantity = this.amount.quantity, + participants = this.participants.toMutableSet(), + owner = this.owner, + quantity = this.amount.quantity, currency = this.amount.token.product.currencyCode, - _issuerParty = this.amount.token.issuer.party, - _issuerRef = this.amount.token.issuer.reference + issuerParty = this.amount.token.issuer.party, + issuerRef = this.amount.token.issuer.reference ) is SampleCashSchemaV3 -> SampleCashSchemaV3.PersistentCashState( participants = this.participants.toMutableSet(), diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt index bea02485cf..f20c421ac1 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt @@ -7,37 +7,26 @@ import net.corda.core.utilities.OpaqueBytes import javax.persistence.* /** - * Second version of a cash contract ORM schema that extends the common - * [VaultFungibleState] abstract schema + * Second version of a cash contract ORM schema that extends the [CommonSchemaV1.FungibleState] abstract schema. */ object SampleCashSchemaV2 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 2, mappedTypes = listOf(PersistentCashState::class.java)) { @Entity - @Table(name = "cash_states_v2", - indexes = arrayOf(Index(name = "ccy_code_idx2", columnList = "ccy_code"))) + @Table(name = "cash_states_v2", indexes = [Index(name = "ccy_code_idx2", columnList = "ccy_code")]) class PersistentCashState( - - @ElementCollection - @Column(name = "participants") - @CollectionTable(name="cash_states_v2_participants", joinColumns = arrayOf( - JoinColumn(name = "output_index", referencedColumnName = "output_index"), - JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"))) - override var participants: MutableSet? = null, - /** product type */ @Column(name = "ccy_code", length = 3) var currency: String, + participants: Set, + owner: AbstractParty, + quantity: Long, + issuerParty: AbstractParty, + issuerRef: OpaqueBytes + ) : CommonSchemaV1.FungibleState(participants.toMutableSet(), owner, quantity, issuerParty, issuerRef.bytes) { - /** parent attributes */ - @Transient - val _participants: Set, - @Transient - val _owner: AbstractParty, - @Transient - val _quantity: Long, - @Transient - val _issuerParty: AbstractParty, - @Transient - val _issuerRef: OpaqueBytes - ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes) + @ElementCollection + @Column(name = "participants") + @CollectionTable(name="cash_states_v2_participants", joinColumns = [JoinColumn(name = "output_index", referencedColumnName = "output_index"), JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id")]) + override var participants: MutableSet? = null + } } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt index fb646d123b..bd854e69de 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt @@ -18,17 +18,8 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { @Entity @Table(name = "cp_states_v2", - indexes = arrayOf(Index(name = "ccy_code_index2", columnList = "ccy_code"), - Index(name = "maturity_index2", columnList = "maturity_instant"))) + indexes = [Index(name = "ccy_code_index2", columnList = "ccy_code"), Index(name = "maturity_index2", columnList = "maturity_instant")]) class PersistentCommercialPaperState( - - @ElementCollection - @Column(name = "participants") - @CollectionTable(name="cp_states_v2_participants", joinColumns = arrayOf( - JoinColumn(name = "output_index", referencedColumnName = "output_index"), - JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"))) - override var participants: MutableSet? = null, - @Column(name = "maturity_instant") var maturity: Instant, @@ -42,17 +33,16 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray, - /** parent attributes */ - @Transient - val _participants: Set, - @Transient - val _owner: AbstractParty, - @Transient - // face value - val _quantity: Long, - @Transient - val _issuerParty: AbstractParty, - @Transient - val _issuerRef: OpaqueBytes - ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes) + participants: Set, + owner: AbstractParty, + quantity: Long, + issuerParty: AbstractParty, + issuerRef: OpaqueBytes + ) : CommonSchemaV1.FungibleState(participants.toMutableSet(), owner, quantity, issuerParty, issuerRef.bytes) { + + @ElementCollection + @Column(name = "participants") + @CollectionTable(name = "cp_states_v2_participants", joinColumns = [JoinColumn(name = "output_index", referencedColumnName = "output_index"), JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id")]) + override var participants: MutableSet? = null + } } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index f5c1de3c5d..4f398608d5 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -422,7 +422,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class if (message.contains("Not an entity")) throw VaultQueryException(""" - Please register the entity '${entityClass.name.substringBefore('$')}' + Please register the entity '${entityClass.name}' See https://docs.corda.net/api-persistence.html#custom-schema-registration for more information""") } throw VaultQueryException("Parsing error: ${e.message}") diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index ca4b25c6a8..7500afbddb 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -20,6 +20,7 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; import net.corda.finance.contracts.DealState; import net.corda.finance.contracts.asset.Cash; import net.corda.finance.schemas.CashSchemaV1; +import net.corda.finance.schemas.SampleCashSchemaV2; import net.corda.node.services.api.IdentityServiceInternal; import net.corda.nodeapi.internal.persistence.CordaPersistence; import net.corda.nodeapi.internal.persistence.DatabaseTransaction; @@ -35,7 +36,6 @@ import org.junit.Test; import rx.Observable; import java.io.IOException; -import java.lang.reflect.Field; import java.security.InvalidAlgorithmParameterException; import java.security.cert.CertificateException; import java.util.*; @@ -43,13 +43,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM; -import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static net.corda.core.node.services.vault.Builder.sum; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; import static net.corda.core.utilities.ByteArrays.toHexString; +import static net.corda.testing.core.TestConstants.*; import static net.corda.testing.internal.InternalTestUtilsKt.rigorousMock; -import static net.corda.testing.core.TestConstants.BOC_NAME; -import static net.corda.testing.core.TestConstants.CHARLIE_NAME; -import static net.corda.testing.core.TestConstants.DUMMY_NOTARY_NAME; import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices; import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static org.assertj.core.api.Assertions.assertThat; @@ -70,7 +70,7 @@ public class VaultQueryJavaTests { @Before public void setUp() throws CertificateException, InvalidAlgorithmParameterException { - List cordappPackages = Arrays.asList("net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); + List cordappPackages = asList("net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); IdentityService identitySvc = makeTestIdentityService(MEGA_CORP.getIdentity(), DUMMY_CASH_ISSUER_INFO.getIdentity(), DUMMY_NOTARY.getIdentity()); Pair databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages, @@ -97,6 +97,29 @@ public class VaultQueryJavaTests { * Static queryBy() tests */ + @Test + public void criteriaWithFieldFromMappedSuperclass() throws NoSuchFieldException { + FieldInfo quantity = getField("quantity", SampleCashSchemaV2.PersistentCashState.class); + FieldInfo currency = getField("currency", SampleCashSchemaV2.PersistentCashState.class); + + CriteriaExpression.AggregateFunctionExpression expression = sum(quantity, singletonList(currency), Sort.Direction.ASC); + VaultCustomQueryCriteria criteria = new VaultCustomQueryCriteria(expression, Vault.StateStatus.UNCONSUMED, null); + + database.transaction(tx -> vaultService.queryBy(FungibleAsset.class, criteria)); + } + + @Test + public void criteriaWithFieldFromMappedSuperclassOfSuperclass() throws NoSuchFieldException { + FieldInfo quantity = getField("quantity", SampleCashSchemaV2.PersistentCashState.class); + FieldInfo currency = getField("currency", SampleCashSchemaV2.PersistentCashState.class); + FieldInfo stateRef = getField("stateRef", SampleCashSchemaV2.PersistentCashState.class); + + CriteriaExpression.AggregateFunctionExpression expression = sum(quantity, asList(currency, stateRef), Sort.Direction.ASC); + VaultCustomQueryCriteria criteria = new VaultCustomQueryCriteria(expression, Vault.StateStatus.UNCONSUMED, null); + + database.transaction(tx -> vaultService.queryBy(FungibleAsset.class, criteria)); + } + @Test public void unconsumedLinearStates() throws VaultQueryException { database.transaction(tx -> { @@ -126,7 +149,7 @@ public class VaultQueryJavaTests { List stateRefs = stateRefsStream.collect(Collectors.toList()); SortAttribute.Standard sortAttribute = new SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID); - Sort sorting = new Sort(Collections.singletonList(new Sort.SortColumn(sortAttribute, Sort.Direction.ASC))); + Sort sorting = new Sort(singletonList(new Sort.SortColumn(sortAttribute, Sort.Direction.ASC))); VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, null, stateRefs); Vault.Page results = vaultService.queryBy(DummyLinearContract.State.class, criteria, sorting); @@ -171,7 +194,7 @@ public class VaultQueryJavaTests { @Test public void consumedDealStatesPagedSorted() throws VaultQueryException { - List dealIds = Arrays.asList("123", "456", "789"); + List dealIds = asList("123", "456", "789"); @SuppressWarnings("unchecked") Triple, UniqueIdentifier, Vault> ids = database.transaction((DatabaseTransaction tx) -> { @@ -184,18 +207,18 @@ public class VaultQueryJavaTests { database.transaction(tx -> { // consume states vaultFiller.consumeDeals((List>) ids.getThird().getStates()); - vaultFiller.consumeLinearStates(Collections.singletonList(ids.getFirst())); + vaultFiller.consumeLinearStates(singletonList(ids.getFirst())); return tx; }); database.transaction(tx -> { // DOCSTART VaultJavaQueryExample2 Vault.StateStatus status = Vault.StateStatus.CONSUMED; @SuppressWarnings("unchecked") - Set> contractStateTypes = new HashSet(Collections.singletonList(LinearState.class)); + Set> contractStateTypes = new HashSet(singletonList(LinearState.class)); QueryCriteria vaultCriteria = new VaultQueryCriteria(status, contractStateTypes); - List linearIds = Collections.singletonList(ids.getSecond()); + List linearIds = singletonList(ids.getSecond()); QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds, Vault.StateStatus.UNCONSUMED, null); QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds); @@ -234,8 +257,8 @@ public class VaultQueryJavaTests { // DOCSTART VaultJavaQueryExample3 QueryCriteria generalCriteria = new VaultQueryCriteria(Vault.StateStatus.ALL); - Field attributeCurrency = CashSchemaV1.PersistentCashState.class.getDeclaredField("currency"); - Field attributeQuantity = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); + FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class); + FieldInfo attributeQuantity = getField("pennies", CashSchemaV1.PersistentCashState.class); CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD"); CriteriaExpression quantityIndex = Builder.greaterThanOrEqual(attributeQuantity, 10L); @@ -275,7 +298,7 @@ public class VaultQueryJavaTests { database.transaction(tx -> { // DOCSTART VaultJavaQueryExample4 @SuppressWarnings("unchecked") - Set> contractStateTypes = new HashSet(Collections.singletonList(Cash.State.class)); + Set> contractStateTypes = new HashSet(singletonList(Cash.State.class)); VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, contractStateTypes); DataFeed, Vault.Update> results = vaultService.trackBy(ContractState.class, criteria); @@ -292,7 +315,7 @@ public class VaultQueryJavaTests { @Test public void trackDealStatesPagedSorted() { - List dealIds = Arrays.asList("123", "456", "789"); + List dealIds = asList("123", "456", "789"); UniqueIdentifier uid = database.transaction(tx -> { Vault states = vaultFiller.fillWithSomeTestLinearStates(10, null); @@ -303,11 +326,11 @@ public class VaultQueryJavaTests { database.transaction(tx -> { // DOCSTART VaultJavaQueryExample5 @SuppressWarnings("unchecked") - Set> contractStateTypes = new HashSet(Arrays.asList(DealState.class, LinearState.class)); + Set> contractStateTypes = new HashSet(asList(DealState.class, LinearState.class)); QueryCriteria vaultCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, contractStateTypes); - List linearIds = Collections.singletonList(uid); - List dealParty = Collections.singletonList(MEGA_CORP.getParty()); + List linearIds = singletonList(uid); + List dealParty = singletonList(MEGA_CORP.getParty()); QueryCriteria dealCriteria = new LinearStateQueryCriteria(dealParty, null, dealIds); QueryCriteria linearCriteria = new LinearStateQueryCriteria(dealParty, linearIds, Vault.StateStatus.UNCONSUMED, null); QueryCriteria dealOrLinearIdCriteria = dealCriteria.or(linearCriteria); @@ -351,9 +374,9 @@ public class VaultQueryJavaTests { database.transaction(tx -> { try { // DOCSTART VaultJavaQueryExample21 - Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); + FieldInfo pennies = getField("pennies", CashSchemaV1.PersistentCashState.class); - QueryCriteria sumCriteria = new VaultCustomQueryCriteria(Builder.sum(pennies)); + QueryCriteria sumCriteria = new VaultCustomQueryCriteria(sum(pennies)); QueryCriteria countCriteria = new VaultCustomQueryCriteria(Builder.count(pennies)); QueryCriteria maxCriteria = new VaultCustomQueryCriteria(Builder.max(pennies)); QueryCriteria minCriteria = new VaultCustomQueryCriteria(Builder.min(pennies)); @@ -397,14 +420,14 @@ public class VaultQueryJavaTests { database.transaction(tx -> { try { // DOCSTART VaultJavaQueryExample22 - Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); - Field currency = CashSchemaV1.PersistentCashState.class.getDeclaredField("currency"); + FieldInfo pennies = getField("pennies", CashSchemaV1.PersistentCashState.class); + FieldInfo currency = getField("currency", CashSchemaV1.PersistentCashState.class); - QueryCriteria sumCriteria = new VaultCustomQueryCriteria(Builder.sum(pennies, Collections.singletonList(currency))); + QueryCriteria sumCriteria = new VaultCustomQueryCriteria(sum(pennies, singletonList(currency))); QueryCriteria countCriteria = new VaultCustomQueryCriteria(Builder.count(pennies)); - QueryCriteria maxCriteria = new VaultCustomQueryCriteria(Builder.max(pennies, Collections.singletonList(currency))); - QueryCriteria minCriteria = new VaultCustomQueryCriteria(Builder.min(pennies, Collections.singletonList(currency))); - QueryCriteria avgCriteria = new VaultCustomQueryCriteria(Builder.avg(pennies, Collections.singletonList(currency))); + QueryCriteria maxCriteria = new VaultCustomQueryCriteria(Builder.max(pennies, singletonList(currency))); + QueryCriteria minCriteria = new VaultCustomQueryCriteria(Builder.min(pennies, singletonList(currency))); + QueryCriteria avgCriteria = new VaultCustomQueryCriteria(Builder.avg(pennies, singletonList(currency))); QueryCriteria criteria = sumCriteria.and(countCriteria).and(maxCriteria).and(minCriteria).and(avgCriteria); Vault.Page results = vaultService.queryBy(Cash.State.class, criteria); @@ -457,10 +480,10 @@ public class VaultQueryJavaTests { database.transaction(tx -> { try { // DOCSTART VaultJavaQueryExample23 - Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); - Field currency = CashSchemaV1.PersistentCashState.class.getDeclaredField("currency"); - Field issuerPartyHash = CashSchemaV1.PersistentCashState.class.getDeclaredField("issuerPartyHash"); - QueryCriteria sumCriteria = new VaultCustomQueryCriteria(Builder.sum(pennies, Arrays.asList(issuerPartyHash, currency), Sort.Direction.DESC)); + FieldInfo pennies = getField("pennies", CashSchemaV1.PersistentCashState.class); + FieldInfo currency = getField("currency", CashSchemaV1.PersistentCashState.class); + FieldInfo issuerPartyHash = getField("issuerPartyHash", CashSchemaV1.PersistentCashState.class); + QueryCriteria sumCriteria = new VaultCustomQueryCriteria(sum(pennies, asList(issuerPartyHash, currency), Sort.Direction.DESC)); Vault.Page results = vaultService.queryBy(Cash.State.class, sumCriteria); // DOCEND VaultJavaQueryExample23 diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 94b9929e98..3271385473 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -21,6 +21,7 @@ import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1.PersistentCashState import net.corda.finance.schemas.CommercialPaperSchemaV1 +import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -29,12 +30,18 @@ import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.testing.core.* import net.corda.testing.internal.TEST_TX_TIME import net.corda.testing.internal.rigorousMock -import net.corda.testing.internal.vault.* +import net.corda.testing.internal.vault.DUMMY_LINEAR_CONTRACT_PROGRAM_ID +import net.corda.testing.internal.vault.DummyLinearContract +import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 +import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.makeTestIdentityService import org.assertj.core.api.Assertions.assertThat -import org.junit.* +import org.junit.ClassRule +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test import org.junit.rules.ExpectedException import org.junit.rules.ExternalResource import java.lang.Thread.sleep @@ -229,6 +236,34 @@ abstract class VaultQueryTestsBase : VaultQueryParties { /** Generic Query tests (combining both FungibleState and LinearState contract types) */ + @Test + fun `criteria with field from mapped superclass`() { + database.transaction { + val expression = builder { + SampleCashSchemaV2.PersistentCashState::quantity.sum( + groupByColumns = listOf(SampleCashSchemaV2.PersistentCashState::currency), + orderBy = Sort.Direction.ASC + ) + } + val criteria = VaultCustomQueryCriteria(expression) + vaultService.queryBy>(criteria) + } + } + + @Test + fun `criteria with field from mapped superclass of superclass`() { + database.transaction { + val expression = builder { + SampleCashSchemaV2.PersistentCashState::quantity.sum( + groupByColumns = listOf(SampleCashSchemaV2.PersistentCashState::currency, SampleCashSchemaV2.PersistentCashState::stateRef), + orderBy = Sort.Direction.ASC + ) + } + val criteria = VaultCustomQueryCriteria(expression) + vaultService.queryBy>(criteria) + } + } + @Test fun `unconsumed states simple`() { database.transaction { From 7ac7e9911e0b670f6ab6c940add3343d981badf6 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 7 May 2018 12:09:21 +0100 Subject: [PATCH 18/18] Adds mailing list and Twitter links. Removes Discourse link. (#3081) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 974ceae490..f567c9a15e 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,17 @@ Corda is a decentralised database system in which nodes trust each other as litt ## Useful links * [Project Website](https://corda.net) +* [Mailing Lists](https://www.corda.net/mailing-lists/) * [Documentation](https://docs.corda.net) +* [Stack Overflow Tag](https://stackoverflow.com/questions/tagged/corda) * [Slack Channel](https://slack.corda.net/) -* [Stack Overflow tag](https://stackoverflow.com/questions/tagged/corda) -* [Forum](https://discourse.corda.net) +* [Twitter](https://twitter.com/cordadlt) * [Meetups](https://www.meetup.com/pro/corda/) * [Training Courses](https://www.corda.net/corda-training/) ## Contributing -Please read [here](./CONTRIBUTING.md). +We welcome contributions to Corda! Please see our [CONTRIBUTING.md](./CONTRIBUTING.md). ## License