diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 518069b359..d62eaf2220 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,6 +14,8 @@ + + @@ -189,4 +191,4 @@ - \ No newline at end of file + 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) diff --git a/README.md b/README.md index f548f65d15..463af1b9a2 100644 --- a/README.md +++ b/README.md @@ -36,16 +36,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 diff --git a/build.gradle b/build.gradle index a5a955b62b..09ed5ac57b 100644 --- a/build.gradle +++ b/build.gradle @@ -94,6 +94,7 @@ buildscript { ext.curator_version = '4.0.0' ext.jsch_version = '0.1.54' ext.protonj_version = '0.27.1' + 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/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 1e53eecaad..08e719a232 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 @@ -17,6 +17,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 @@ -77,8 +79,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 @@ -227,16 +242,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)) @@ -249,31 +271,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) @@ -292,19 +340,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) = @@ -312,19 +372,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) = @@ -332,7 +404,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) = @@ -340,8 +417,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/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt index b958602c3f..15bf4a7ea2 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt @@ -12,6 +12,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 @@ -29,13 +30,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) @@ -52,7 +57,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) @@ -62,7 +69,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) @@ -72,7 +81,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) @@ -83,7 +94,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) @@ -94,7 +107,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) @@ -109,7 +124,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/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/docs/source/changelog.rst b/docs/source/changelog.rst index e1ef09876c..d1df15292f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,8 +7,13 @@ 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 +* 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/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 diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index e13bfe6bfa..3d646111c5 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -61,8 +61,8 @@ This command line will start the node with JMX metrics accessible via HTTP on po See :ref:`Monitoring your node ` for further details. -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. @@ -73,4 +73,20 @@ Start the nodes with ``runnodes`` by running the following command from the root * Windows: ``call build\nodes\runnodes.bat`` .. warning:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may -fail to start. \ No newline at end of file + fail to start. + +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. 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..26313b1d3c --- /dev/null +++ b/experimental/blobinspector/src/test/kotlin/net/corda/blobinspector/InMemoryTests.kt @@ -0,0 +1,91 @@ +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 net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT +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"), 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)))), AMQP_P2P_CONTEXT)) + } + + @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, AMQP_P2P_CONTEXT)) + } + + @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") + )), AMQP_P2P_CONTEXT)) + } + + @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") + ) + )), AMQP_P2P_CONTEXT)) + } + + @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")), + AMQP_P2P_CONTEXT + )) + } +} \ 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 0000000000..450e6970da Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Composite differ diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Int b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Int new file mode 100644 index 0000000000..25dcb48d65 Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1Int differ 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 0000000000..9676f0375f Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.1String differ 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 0000000000..0bf3a5c475 Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Composite differ 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 0000000000..118a23f37b Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.2Int differ 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 0000000000..9f00d59068 Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.3Int differ diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.IntList b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.IntList new file mode 100644 index 0000000000..d762a9e821 Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.IntList differ 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 0000000000..175949d9aa Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntClass differ 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 0000000000..67ba352ec4 Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.MapIntString differ 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 0000000000..5758d9fa62 Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/FileParseTests.StringList differ diff --git a/experimental/blobinspector/src/test/resources/net/corda/blobinspector/networkParams b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/networkParams new file mode 100644 index 0000000000..dcdbaa7b5f Binary files /dev/null and b/experimental/blobinspector/src/test/resources/net/corda/blobinspector/networkParams differ 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 2760efcc27..9ef43218e7 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 @@ -60,12 +60,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/sampleschemas/SampleCashSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/sampleschemas/SampleCashSchemaV2.kt index d2ffda6b8f..505dd914ff 100644 --- a/finance/src/test/kotlin/net/corda/finance/sampleschemas/SampleCashSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/sampleschemas/SampleCashSchemaV2.kt @@ -17,8 +17,7 @@ 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)) { @@ -26,31 +25,21 @@ object SampleCashSchemaV2 : MappedSchema(schemaFamily = CashSchema.javaClass, ve override val migrationResource = "sample-cash-v2.changelog-init" @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/sampleschemas/SampleCommercialPaperSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/sampleschemas/SampleCommercialPaperSchemaV2.kt index a6887f7dde..f631084167 100644 --- a/finance/src/test/kotlin/net/corda/finance/sampleschemas/SampleCommercialPaperSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/sampleschemas/SampleCommercialPaperSchemaV2.kt @@ -31,17 +31,8 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @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, @@ -55,17 +46,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-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 e16446d2d8..dbf93ec26e 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 @@ -361,7 +361,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 @@ -438,7 +438,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 4c9bd812ab..ad5a22bae9 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 @@ -130,7 +130,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) } @@ -143,7 +142,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 -> { 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 b531fce895..6c72829fca 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 @@ -90,10 +90,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)!!) @@ -172,7 +172,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 e1550d296f..bda44ea16f 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 @@ -76,5 +76,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 60004f0d44..d469cb688d 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 @@ -17,8 +17,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 1b5d3ed93d..47bb652013 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 @@ -49,13 +49,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) @@ -90,7 +92,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() @@ -153,8 +155,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 8cfd815c22..2ac9849a6e 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 @@ -62,5 +62,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 605f098662..c2307bddbe 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 @@ -15,7 +15,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 762d5bcb21..c3e8aaea5a 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 @@ -20,7 +20,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 081dfb8222..ce74965c01 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 @@ -10,6 +10,7 @@ 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 @@ -28,7 +29,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 { @@ -39,5 +47,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 d2240540f2..88c9f1bbf5 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 @@ -16,8 +16,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 @@ -41,12 +41,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 @@ -62,7 +62,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() @@ -74,7 +74,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) @@ -131,8 +131,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)) { @@ -145,19 +144,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 820c7f8951..458e4e47f6 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 @@ -10,6 +10,7 @@ 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 @@ -40,10 +41,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 b65ab9e6f4..d7bd52cca5 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 @@ -10,6 +10,7 @@ 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 @@ -42,7 +43,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) } @@ -56,20 +58,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") } @@ -118,20 +124,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) + } } } @@ -145,47 +155,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 63ebf26045..d5f027e211 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 @@ -11,6 +11,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 @@ -18,15 +19,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)}") } @@ -60,7 +60,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> = @@ -83,12 +84,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) } } } @@ -97,8 +99,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 5cf9fea470..e2038fb626 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 @@ -11,6 +11,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 @@ -74,22 +75,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 c8eaef09eb..b6ace5113a 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 @@ -11,6 +11,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 @@ -50,13 +51,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 @@ -87,12 +91,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) } } @@ -134,7 +142,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) } @@ -148,17 +161,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) } } @@ -186,11 +203,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 78d4402516..1c2cb29c44 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 @@ -14,6 +14,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 @@ -45,7 +46,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 { @@ -70,9 +71,10 @@ 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.") + val amqpSequence = amqpMagic.consume(byteSequence) + ?: throw NotSerializableException("Serialization header does not match.") var stream: InputStream = ByteBufferInputStream(amqpSequence) try { while (true) { @@ -89,25 +91,27 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto stream.close() } } - } - @Throws(NotSerializableException::class) - inline fun deserialize(bytes: SerializedBytes): T = deserialize(bytes, T::class.java) - - @Throws(NotSerializableException::class) - 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) + 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, context: SerializationContext): T = + deserialize(bytes, T::class.java, context) + + @Throws(NotSerializableException::class) private fun des(generator: () -> R): R { try { @@ -127,23 +131,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) - 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 { - val envelope = getEnvelope(bytes) + 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() @@ -164,19 +182,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 d3d60ea93e..d45febb609 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 @@ -11,6 +11,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 @@ -75,7 +76,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 @@ -127,7 +129,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) { @@ -141,7 +145,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 e481950924..15b6fbfdb3 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 @@ -10,6 +10,7 @@ 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 @@ -38,7 +39,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<*>? @@ -50,7 +53,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/Envelope.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt index 9b27e264c1..b8941c7143 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 @@ -39,7 +39,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/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 6f6ddac708..f08f9dce2c 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 @@ -11,10 +11,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 @@ -49,12 +50,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 { @@ -106,7 +108,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( @@ -118,7 +120,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) } /** @@ -153,14 +155,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") } } @@ -170,7 +173,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 @@ -180,15 +183,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.") } } @@ -201,18 +205,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 8eb5c4332b..d12125d90b 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 @@ -11,6 +11,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 @@ -18,9 +19,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<*, *> @@ -28,8 +26,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)}") @@ -67,7 +65,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> = @@ -90,6 +89,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 @@ -98,22 +98,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 447ca44181..c903a13019 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 @@ -10,6 +10,7 @@ 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 @@ -67,6 +68,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 && @@ -82,7 +84,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) } } } @@ -91,16 +93,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") @@ -110,12 +113,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 }) } @@ -123,7 +127,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( @@ -133,7 +138,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 61dbd7ec3d..9786d24998 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 @@ -10,6 +10,7 @@ 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 @@ -19,8 +20,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() @@ -86,12 +87,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})" @@ -106,11 +110,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)) @@ -122,18 +130,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 5e7e17765c..c7b72c646f 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 @@ -12,12 +12,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? @@ -151,6 +151,7 @@ class PropertyAccessorGetterSetter( */ setter.isAccessible = true } + /** * Invokes the setter on the underlying object passing in the serialized value. */ @@ -172,7 +173,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.") } } @@ -197,7 +198,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") } } } @@ -206,7 +207,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 950099ffcc..e231532633 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 @@ -105,7 +105,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 { @@ -173,8 +173,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() } @@ -255,9 +254,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 @@ -277,8 +276,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") @@ -325,7 +324,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 2f4c862362..9b29316260 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 @@ -10,6 +10,7 @@ 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 @@ -33,7 +34,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() @@ -44,19 +48,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 { @@ -70,11 +73,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) @@ -97,8 +100,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) { @@ -109,15 +112,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) @@ -126,7 +129,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 53ebafe871..6d3c8f10f6 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 @@ -15,9 +15,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.* @@ -69,8 +71,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 @@ -109,7 +110,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) } @@ -254,8 +256,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 @@ -293,12 +295,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 3dbbc5456f..91f068ef43 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 @@ -10,6 +10,7 @@ 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 @@ -33,13 +34,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 8b71bce9ba..4b2a33886e 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 @@ -224,8 +224,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) @@ -298,8 +298,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 d844af4d9c..378b3580c9 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 @@ -12,6 +12,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 e9d96784c8..7a12a2fac1 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 @@ -10,6 +10,7 @@ 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 @@ -25,7 +26,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 @@ -44,8 +47,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 24094b54e2..d9fa3ce32c 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 @@ -12,12 +12,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 6b1217c9f0..a8ba4ec5fb 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 @@ -12,7 +12,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 7945b3f9e1..7fad041c1e 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 @@ -12,7 +12,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 50478ea8e2..d67db18099 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 @@ -12,7 +12,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 50469d9cd0..9cba34b0b8 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 @@ -11,7 +11,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 @@ -25,13 +27,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 99b91eaf71..3339f2de17 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 @@ -11,6 +11,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 @@ -22,13 +23,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 2b7f026d14..1c95dad698 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 @@ -10,6 +10,7 @@ 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 @@ -26,12 +27,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 1ef4549915..782aa7c19f 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 @@ -10,6 +10,7 @@ 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 @@ -26,12 +27,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 a849819897..c4b9758a5e 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 @@ -12,7 +12,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 f5bff3f10d..631432e65a 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 @@ -12,7 +12,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 fa09cfa381..aaed150908 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 @@ -12,7 +12,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 f93ce58ff4..042784b0af 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 @@ -12,11 +12,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() @@ -130,7 +130,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 45efb11d5c..298cd5e1e4 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 @@ -36,11 +36,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 460f73197f..d64dc3017f 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 @@ -19,18 +19,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 37905fcd15..3695c3bdca 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 @@ -268,7 +268,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 fa9606b8ee..24c612bb1d 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 @@ -10,7 +10,6 @@ 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 @@ -22,14 +21,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)) @@ -96,7 +95,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 c638fff0f3..27b96c6b13 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 @@ -13,7 +13,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 ea4b9f7e5c..23c0c79971 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 @@ -24,12 +24,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 035b5f8b24..c7932721ee 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 @@ -11,6 +11,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; @@ -51,7 +53,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 f957ae3147..1bdaabdd78 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 @@ -12,6 +12,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; @@ -43,10 +44,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()); } @@ -58,13 +59,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 { @@ -75,13 +76,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 e11227b9ed..fbc86a1f89 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 @@ -11,6 +11,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.*; @@ -93,7 +94,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); } @@ -108,7 +109,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); } @@ -122,7 +123,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); @@ -138,7 +139,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); } @@ -153,7 +154,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); @@ -185,7 +186,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 bdc59a8eb0..a263406d81 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 @@ -10,6 +10,7 @@ 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; @@ -43,6 +44,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 99176eefdb..153d4fb5c8 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 @@ -16,6 +16,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; @@ -199,7 +200,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(); @@ -219,13 +220,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 25ba853d26..d865cff4c4 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 @@ -13,6 +13,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; @@ -142,9 +143,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 2c4f9dd9aa..52182b9851 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 @@ -12,6 +12,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.*; @@ -142,7 +143,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()); @@ -157,7 +158,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()); @@ -174,7 +175,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()); @@ -190,7 +191,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()); @@ -222,9 +223,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); @@ -234,8 +235,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); @@ -249,8 +250,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); @@ -263,8 +265,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); @@ -290,8 +295,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); @@ -300,7 +307,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( @@ -313,12 +320,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( @@ -331,7 +339,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); } @@ -357,6 +366,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 e5380afd81..0000000000 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.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 20898f309e..9ffe50eaa4 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 @@ -13,9 +13,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 57e7c26dea..36b266b5de 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 @@ -11,10 +11,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 c17ffe468c..2e9f1a1c1a 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 @@ -10,9 +10,15 @@ 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 a78bc414b1..42272c1655 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 @@ -11,9 +11,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 af0c6edba3..bb88b16faf 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 @@ -11,9 +11,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 1e9d5b5d53..b2a88a7ecf 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 @@ -15,6 +15,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 21f93fc9f9..86a1ccd93d 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 @@ -10,8 +10,12 @@ 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 f547b546ac..c9713d952a 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 @@ -11,6 +11,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 @@ -20,6 +22,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 efbec4bbf8..cb28a4e19a 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 @@ -14,6 +14,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 @@ -22,6 +24,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 3e80e31d9b..978507bfa0 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 @@ -13,12 +13,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 02f694782f..4e84fae705 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 @@ -10,10 +10,17 @@ 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 bd5408b2f2..fd9fad89b6 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 @@ -18,6 +18,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 @@ -28,6 +30,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 9168f7792c..b5d3f21f71 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 @@ -14,6 +14,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 84db597caa..9767de16dd 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 @@ -17,12 +17,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 @@ -311,7 +307,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 d16e881688..56c6ab7624 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 @@ -23,11 +23,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 dad1a4696d..4efb054eae 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 @@ -13,11 +13,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 1ac7a84d37..002ee156cc 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 @@ -33,6 +33,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 @@ -490,12 +491,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") } @@ -660,14 +661,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 ccc4dde1a9..381a376b82 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 @@ -11,6 +11,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 @@ -18,6 +20,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 4b6235431b..001f2f6ef5 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 @@ -10,10 +10,13 @@ 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 f5ef970f1b..d1c67de023 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 @@ -20,6 +20,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 8a331c54d2..c3892a6a07 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 @@ -14,7 +14,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 c7ee5001f6..2f5dab3ede 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 @@ -18,6 +18,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 7213ac7b0f..e3a97502eb 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 @@ -15,6 +15,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 30deee8ea7..acc6e8426e 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 @@ -17,6 +17,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 adc7b7d791..88bb90e5fa 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 @@ -16,6 +16,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 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 index c1d7d203b3..2fa15771ef 100644 --- 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 @@ -124,4 +124,4 @@ class ScheduledFlowIntegrationTests : IntegrationTest() { assertEquals(aliceSpentStates.count(), bobSpentStates.count()) } } -} \ No newline at end of file +} diff --git a/node/src/main/kotlin/net/corda/node/Corda.kt b/node/src/main/kotlin/net/corda/node/Corda.kt index 58ff6eeaac..f6eb401079 100644 --- a/node/src/main/kotlin/net/corda/node/Corda.kt +++ b/node/src/main/kotlin/net/corda/node/Corda.kt @@ -19,11 +19,8 @@ import kotlin.system.exitProcess import net.corda.node.internal.EnterpriseNode 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. exitProcess(if (EnterpriseNode.Startup(args).run()) 0 else 1) -} \ No newline at end of file +} 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 760711f244..bc95b912bf 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt @@ -20,7 +20,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 @@ -39,8 +38,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/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index baa8f3bfc0..0991826bef 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -11,6 +11,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 @@ -66,6 +67,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) { 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 04ba81567a..82128d6ded 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 @@ -432,7 +432,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 569bfe45c4..783a55b060 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 @@ -30,6 +30,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; @@ -46,7 +47,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.*; @@ -54,8 +54,10 @@ 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; @@ -79,7 +81,7 @@ public class VaultQueryJavaTests { @Before public void setUp() throws CertificateException, InvalidAlgorithmParameterException { - List cordappPackages = Arrays.asList( + List cordappPackages = asList( "net.corda.testing.internal.vault", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName(), @@ -111,6 +113,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 -> { @@ -140,7 +165,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); @@ -185,7 +210,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) -> { @@ -198,18 +223,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); @@ -248,8 +273,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); @@ -289,7 +314,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); @@ -306,7 +331,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); @@ -317,11 +342,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); @@ -365,9 +390,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)); @@ -411,14 +436,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); @@ -471,10 +496,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/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index da0e130544..84682618c7 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 @@ -295,20 +295,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() @@ -316,15 +317,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") @@ -338,25 +339,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() } } 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 dacfb870ef..de4d5e827c 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 @@ -24,6 +24,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 @@ -37,6 +38,7 @@ class NetworkParametersReaderTest { @JvmField val testSerialization = SerializationEnvironmentRule(true) + val fs = Jimfs.newFileSystem(Configuration.unix()) private val cacheTimeout = 100000.seconds private lateinit var server: NetworkMapServer @@ -52,11 +54,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) @@ -70,4 +72,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 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 305c809c7a..09fc08a053 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 @@ -10,7 +10,6 @@ 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.* @@ -21,74 +20,26 @@ import net.corda.finance.sampleschemas.SampleCashSchemaV3 import net.corda.finance.schemas.CashSchemaV1 import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseTransaction +import net.corda.finance.schemas.SampleCashSchemaV3 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() @@ -97,16 +48,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`() { @@ -123,4 +67,4 @@ class VaultQueryExceptionsTests { }.isInstanceOf(VaultQueryException::class.java).hasMessageContaining("Please register the entity") } } -} \ No newline at end of file +} 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 980189f503..280000823d 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 @@ -15,6 +15,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.* @@ -31,6 +32,8 @@ import net.corda.finance.sampleschemas.SampleCashSchemaV3 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 import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -38,13 +41,20 @@ 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 import java.time.Instant import java.time.LocalDate @@ -52,75 +62,102 @@ import java.time.ZoneOffset import java.time.temporal.ChronoUnit import java.util.* -open 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, - 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 + 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) } @AfterClass @JvmStatic @@ -129,25 +166,38 @@ open class VaultQueryTests { } } - private lateinit var transaction: DatabaseTransaction + override fun after() { + database.close() + } +} - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule() +class VaultQueryRollbackRule(val vaultQueryParties: VaultQueryParties) : ExternalResource() { + + 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 - open fun setUp() { - transaction = database.newTransaction() - } - - @After - open fun tearDown() { - transaction.rollback() - transaction.close() + @Suppress("LeakingThis") + @Rule + @JvmField + val transactionRule = VaultQueryRollbackRule(this) + companion object { + @ClassRule @JvmField + val testSerialization = SerializationEnvironmentRule() } /** @@ -200,6 +250,34 @@ open class VaultQueryTests { /** 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 { @@ -2179,4 +2257,12 @@ open 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) */ -} \ No newline at end of file +} + +class VaultQueryTests : VaultQueryTestsBase(), VaultQueryParties by vaultQueryTestRule { + + companion object { + @ClassRule @JvmField + val vaultQueryTestRule = VaultQueryTestRule() + } +} diff --git a/settings.gradle b/settings.gradle index c3220102e7..27044b5e0b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,7 @@ include 'experimental:quasar-hook' include 'experimental:kryo-hook' include 'experimental:intellij-plugin' include 'experimental:flow-hook' +include 'experimental:blobinspector' include 'test-common' include 'test-utils' include 'smoke-test-utils' 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 fda41a3e44..d1293bfc19 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 @@ -13,6 +13,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 @@ -38,9 +40,13 @@ import org.assertj.core.api.Assertions.* import org.json.simple.JSONObject import org.junit.ClassRule 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 : IntegrationTest() { companion object { @@ -186,5 +192,31 @@ class DriverTests : IntegrationTest() { } } + + @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/MockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt index 500121ff4e..9861c8b59a 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 @@ -43,17 +43,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) + } } /** @@ -258,14 +264,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))) } @@ -280,14 +287,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/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 36cca2c21c..c10bfe7690 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 @@ -82,6 +82,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 @@ -117,8 +118,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()) @@ -623,20 +629,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/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 437bf2927e..51dd476c6e 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 @@ -83,7 +83,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( @@ -91,12 +92,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, @@ -227,7 +232,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 { @@ -379,7 +385,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() 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 78f383b4b2..e407d0e8de 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 @@ -13,7 +13,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 @@ -22,6 +25,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 } @@ -42,6 +46,7 @@ class ShutdownManager(private val executorService: ExecutorService) { } fun shutdown() { + state.locked { isShuttingDown = true } val shutdownActionFutures = state.locked { if (isShutdown) { emptyList Unit>>() @@ -111,4 +116,8 @@ class ShutdownManager(private val executorService: ExecutorService) { } } } + + fun isShuttingDown(): Boolean { + return state.locked { isShuttingDown } + } } \ No newline at end of file