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