Merge remote-tracking branch 'corda/master' into christians_os_merge_20171031

This commit is contained in:
Christian Sailer
2017-11-02 14:41:18 +00:00
1211 changed files with 3627 additions and 432161 deletions

View File

@ -11,6 +11,10 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
compile "commons-beanutils:commons-beanutils:${beanutils_version}"
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
compile "org.apache.activemq:artemis-commons:${artemis_version}"

View File

@ -1,7 +1,5 @@
package net.corda.nodeapi
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.messaging.MessageRecipientGroup
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
@ -42,11 +40,6 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
val hostAndPort: NetworkHostAndPort
}
@CordaSerializable
data class NetworkMapAddress(override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
override val queueName: String get() = NETWORK_MAP_QUEUE
}
/**
* This is the class used to implement [SingleMessageRecipient], for now. Note that in future this class
* may change or evolve and code that relies upon it being a simple host/port may not function correctly.
@ -60,11 +53,8 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
*/
@CordaSerializable
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
companion object {
fun asSingleNode(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort): NodeAddress {
return NodeAddress("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort)
}
}
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
this("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort)
}
/**
@ -82,13 +72,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
/** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */
abstract val config: SSLConfiguration?
// Used for bridges creation.
fun getArtemisPeerAddress(party: Party, address: NetworkHostAndPort, netMapName: CordaX500Name? = null): ArtemisPeerAddress {
return if (party.name == netMapName) {
NetworkMapAddress(address)
} else {
NodeAddress.asSingleNode(party.owningKey, address) // It also takes care of services nodes treated as peer nodes
}
}
}

View File

@ -0,0 +1,38 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.UnsignedLong
/**
* R3 AMQP assigned enterprise number
*
* see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers)
*
* Repeated here for brevity:
* 50530 - R3 - Mike Hearn - mike&r3.com
*/
const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl(32 + 16)
/**
* AMQP desriptor ID's for our custom types.
*
* NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE
*
* these are encoded as part of a serialised blob and doing so would render us unable to
* de-serialise that blob!!!
*/
enum class AMQPDescriptorRegistry(val id: Long) {
ENVELOPE(1),
SCHEMA(2),
OBJECT_DESCRIPTOR(3),
FIELD(4),
COMPOSITE_TYPE(5),
RESTRICTED_TYPE(6),
CHOICE(7),
REFERENCED_OBJECT(8),
TRANSFORM_SCHEMA(9),
TRANSFORM_ELEMENT(10),
TRANSFORM_ELEMENT_KEY(11)
;
val amqpDescriptor = UnsignedLong(id or DESCRIPTOR_TOP_32BITS)
}

View File

@ -0,0 +1,70 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.codec.Data
import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException
/**
* This class wraps all serialized data, so that the schema can be carried along with it. We will provide various
* internal utilities to decompose and recompose with/without schema etc so that e.g. we can store objects with a
* (relationally) normalised out schema to avoid excessive duplication.
*/
// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we
// TODO: don't recognise a type descriptor.
data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: TransformsSchema) : DescribedType {
companion object : DescribedTypeConstructor<Envelope> {
val DESCRIPTOR = AMQPDescriptorRegistry.ENVELOPE.amqpDescriptor
val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR)
// described list should either be two or three elements long
private const val ENVELOPE_WITHOUT_TRANSFORMS = 2
private const val ENVELOPE_WITH_TRANSFORMS = 3
private const val BLOB_IDX = 0
private const val SCHEMA_IDX = 1
private const val TRANSFORMS_SCHEMA_IDX = 2
fun get(data: Data): Envelope {
val describedType = data.`object` as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
val list = describedType.described as List<*>
// We need to cope with objects serialised without the transforms header element in the
// envelope
val transformSchema: Any? = when (list.size) {
ENVELOPE_WITHOUT_TRANSFORMS -> null
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX]
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
}
return newInstance(listOf(list[BLOB_IDX], Schema.get(list[SCHEMA_IDX]!!),
TransformsSchema.newInstance(transformSchema)))
}
// This separation of functions is needed as this will be the entry point for the default
// AMQP decoder if one is used (see the unit tests).
override fun newInstance(described: Any?): Envelope {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
// We need to cope with objects serialised without the transforms header element in the
// envelope
val transformSchema = when (list.size) {
ENVELOPE_WITHOUT_TRANSFORMS -> TransformsSchema.newInstance(null)
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX] as TransformsSchema
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
}
return Envelope(list[BLOB_IDX], list[SCHEMA_IDX] as Schema, transformSchema)
}
override fun getTypeClass(): Class<*> = Envelope::class.java
}
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = listOf(obj, schema, transformsSchema)
}

View File

@ -10,7 +10,6 @@ import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.amqp.UnsignedLong
import org.apache.qpid.proton.codec.Data
import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException
import java.lang.reflect.*
@ -33,61 +32,13 @@ const val DESCRIPTOR_DOMAIN: String = "net.corda"
// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB
val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray())
private enum class DescriptorRegistry(val id: Long) {
ENVELOPE(1),
SCHEMA(2),
OBJECT_DESCRIPTOR(3),
FIELD(4),
COMPOSITE_TYPE(5),
RESTRICTED_TYPE(6),
CHOICE(7),
REFERENCED_OBJECT(8),
;
val amqpDescriptor = UnsignedLong(id or DESCRIPTOR_TOP_32BITS)
}
/**
* This class wraps all serialized data, so that the schema can be carried along with it. We will provide various internal utilities
* to decompose and recompose with/without schema etc so that e.g. we can store objects with a (relationally) normalised out schema to
* avoid excessive duplication.
*/
// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we don't recognise a type descriptor.
data class Envelope(val obj: Any?, val schema: Schema) : DescribedType {
companion object : DescribedTypeConstructor<Envelope> {
val DESCRIPTOR = DescriptorRegistry.ENVELOPE.amqpDescriptor
val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR)
fun get(data: Data): Envelope {
val describedType = data.`object` as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
val list = describedType.described as List<*>
return newInstance(listOf(list[0], Schema.get(list[1]!!)))
}
override fun getTypeClass(): Class<*> = Envelope::class.java
override fun newInstance(described: Any?): Envelope {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
return Envelope(list[0], list[1] as Schema)
}
}
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = listOf(obj, schema)
}
/**
* This and the classes below are OO representations of the AMQP XML schema described in the specification. Their
* [toString] representations generate the associated XML form.
*/
data class Schema(val types: List<TypeNotation>) : DescribedType {
companion object : DescribedTypeConstructor<Schema> {
val DESCRIPTOR = DescriptorRegistry.SCHEMA.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.SCHEMA.amqpDescriptor
fun get(obj: Any): Schema {
val describedType = obj as DescribedType
@ -117,7 +68,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr
constructor(name: String?) : this(Symbol.valueOf(name))
companion object : DescribedTypeConstructor<Descriptor> {
val DESCRIPTOR = DescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor
fun get(obj: Any): Descriptor {
val describedType = obj as DescribedType
@ -155,7 +106,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr
data class Field(val name: String, val type: String, val requires: List<String>, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType {
companion object : DescribedTypeConstructor<Field> {
val DESCRIPTOR = DescriptorRegistry.FIELD.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.FIELD.amqpDescriptor
fun get(obj: Any): Field {
val describedType = obj as DescribedType
@ -215,7 +166,7 @@ sealed class TypeNotation : DescribedType {
data class CompositeType(override val name: String, override val label: String?, override val provides: List<String>, override val descriptor: Descriptor, val fields: List<Field>) : TypeNotation() {
companion object : DescribedTypeConstructor<CompositeType> {
val DESCRIPTOR = DescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor
fun get(describedType: DescribedType): CompositeType {
if (describedType.descriptor != DESCRIPTOR) {
@ -264,7 +215,7 @@ data class RestrictedType(override val name: String,
override val descriptor: Descriptor,
val choices: List<Choice>) : TypeNotation() {
companion object : DescribedTypeConstructor<RestrictedType> {
val DESCRIPTOR = DescriptorRegistry.RESTRICTED_TYPE.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.RESTRICTED_TYPE.amqpDescriptor
fun get(describedType: DescribedType): RestrictedType {
if (describedType.descriptor != DESCRIPTOR) {
@ -309,7 +260,7 @@ data class RestrictedType(override val name: String,
data class Choice(val name: String, val value: String) : DescribedType {
companion object : DescribedTypeConstructor<Choice> {
val DESCRIPTOR = DescriptorRegistry.CHOICE.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.CHOICE.amqpDescriptor
fun get(obj: Any): Choice {
val describedType = obj as DescribedType
@ -338,7 +289,7 @@ data class Choice(val name: String, val value: String) : DescribedType {
data class ReferencedObject(private val refCounter: Int) : DescribedType {
companion object : DescribedTypeConstructor<ReferencedObject> {
val DESCRIPTOR = DescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor
val DESCRIPTOR = AMQPDescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor
fun get(obj: Any): ReferencedObject {
val describedType = obj as DescribedType

View File

@ -45,10 +45,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
val data = Data.Factory.create()
data.withDescribed(Envelope.DESCRIPTOR_OBJECT) {
withList {
// Our object
writeObject(obj, this)
// The schema
writeSchema(Schema(schemaHistory.toList()), this)
val schema = Schema(schemaHistory.toList())
writeSchema(schema, this)
writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this)
}
}
val bytes = ByteArray(data.encodedSize().toInt() + 8)
@ -66,6 +66,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
data.putObject(schema)
}
open fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) {
data.putObject(transformsSchema)
}
internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type) {
if (obj == null) {
data.putNull()

View File

@ -34,6 +34,8 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
open val classCarpenter = ClassCarpenter(cl, whitelist)
val classloader: ClassLoader
get() = classCarpenter.classloader

View File

@ -0,0 +1,76 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename
import net.corda.core.serialization.CordaSerializationTransformRenames
/**
* Utility class that defines an instance of a transform we support.
*
* @property type The transform annotation.
* @property enum Maps the annotaiton onto a transform type, we expect there are multiple annotations that
* would map to a single transform type.
* @property getAnnotations Anonymous function that should return a list of Annotations encapsualted by the parent annotation
* that reference the transform. Notionally this allows the code that extracts transforms to work on single instances
* of a transform or a meta list of them.
*/
data class SupportedTransform(
val type: Class<out Annotation>,
val enum: TransformTypes,
val getAnnotations: (Annotation) -> List<Annotation>)
/**
* Extract from an annotated class the list of annotations that refer to a particular
* transformation type when that class has multiple transforms wrapped in an
* outer annotation
*/
@Suppress("UNCHECKED_CAST")
private val wrapperExtract = { x: Annotation ->
(x::class.java.getDeclaredMethod("value").invoke(x) as Array<Annotation>).toList()
}
/**
* Extract from an annotated class the list of annotations that refer to a particular
* transformation type when that class has a single decorator applied
*/
private val singleExtract = { x: Annotation -> listOf(x) }
// Transform annotation used to test the handling of transforms the de-serialising node doesn't understand. At
// some point test cases will have been created with this transform applied.
// @Target(AnnotationTarget.CLASS)
// @Retention(AnnotationRetention.RUNTIME)
// annotation class UnknownTransformAnnotation(val a: Int, val b: Int, val c: Int)
/**
* Utility list of all transforms we support that simplifies our generation code.
*
* NOTE: We have to support single instances of the transform annotations as well as the wrapping annotation
* when many instances are repeated.
*/
val supportedTransforms = listOf(
SupportedTransform(
CordaSerializationTransformEnumDefaults::class.java,
TransformTypes.EnumDefault,
wrapperExtract
),
SupportedTransform(
CordaSerializationTransformEnumDefault::class.java,
TransformTypes.EnumDefault,
singleExtract
),
SupportedTransform(
CordaSerializationTransformRenames::class.java,
TransformTypes.Rename,
wrapperExtract
),
SupportedTransform(
CordaSerializationTransformRename::class.java,
TransformTypes.Rename,
singleExtract
)
//,SupportedTransform(
// UnknownTransformAnnotation::class.java,
// TransformTypes.UnknownTest,
// singleExtract)
)

View File

@ -0,0 +1,72 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename
import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException
/**
* Enumerated type that represents each transform that can be applied to a class. Used as the key type in
* the [TransformsSchema] map for each class.
*
* @property build should be a function that takes a transform [Annotation] (currently one of
* [CordaSerializationTransformRename] or [CordaSerializationTransformEnumDefaults])
* and constructs an instance of the corresponding [Transform] type
*
* DO NOT REORDER THE CONSTANTS!!! Please append any new entries to the end
*/
// TODO: it would be awesome to auto build this list by scanning for transform annotations themselves
// TODO: annotated with some annotation
enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType {
/**
* Placeholder entry for future transforms where a node receives a transform we've subsequently
* added and thus the de-serialising node doesn't know about that transform.
*/
Unknown({ UnknownTransform() }) {
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal
},
EnumDefault({ a -> EnumDefaultSchemaTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) {
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal
},
Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) {
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal
}
// Transform used to test the unknown handler, leave this at as the final constant, uncomment
// when regenerating test cases - if Java had a pre-processor this would be much neater
//
//,UnknownTest({ a -> UnknownTestTransform((a as UnknownTransformAnnotation).a, a.b, a.c)}) {
// override fun getDescriptor(): Any = DESCRIPTOR
// override fun getDescribed(): Any = ordinal
//}
;
companion object : DescribedTypeConstructor<TransformTypes> {
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor
/**
* Used to construct an instance of the object from the serialised bytes
*
* @param obj the serialised byte object from the AMQP serialised stream
*/
override fun newInstance(obj: Any?): TransformTypes {
val describedType = obj as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
return try {
values()[describedType.described as Int]
} catch (e: IndexOutOfBoundsException) {
values()[0]
}
}
override fun getTypeClass(): Class<*> = TransformTypes::class.java
}
}

View File

@ -0,0 +1,317 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformRename
import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException
import java.util.*
// NOTE: We are effectively going to replicate the annotations, we need to do this because
// we can't instantiate instances of those annotation classes and this code needs to
// work at the de-serialising end
/**
* Base class for representations of specific types of transforms as applied to a type within the
* Corda serialisation framework
*/
abstract class Transform : DescribedType {
companion object : DescribedTypeConstructor<Transform> {
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT.amqpDescriptor
/**
* @param obj: a serialized instance of a described type, should be one of the
* descendants of this class
*/
private fun checkDescribed(obj: Any?): Any? {
val describedType = obj as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
return describedType.described
}
/**
* From an encoded descendant return an instance of the specific type. Transforms are encoded into
* the schema as a list of class name and parameters.Using the class name (list element 0)
* create the appropriate class instance
*
* For future proofing any unknown transform types are not treated as errors, rather we
* simply create a placeholder object so we can ignore it
*
* @param obj: a serialized instance of a described type, should be one of the
* descendants of this class
*/
override fun newInstance(obj: Any?): Transform {
val described = Transform.checkDescribed(obj) as List<*>
return when (described[0]) {
EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described)
RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described)
else -> UnknownTransform()
}
}
override fun getTypeClass(): Class<*> = Transform::class.java
}
override fun getDescriptor(): Any = DESCRIPTOR
/**
* Return a string representation of a transform in terms of key / value pairs, used
* by the serializer to encode arbitrary transforms
*/
abstract fun params(): String
abstract val name: String
}
/**
* Transform type placeholder that allows for backward compatibility. Should a noce recieve
* a transform type it doesn't recognise, we can will use this as a placeholder
*/
class UnknownTransform : Transform() {
companion object : DescribedTypeConstructor<UnknownTransform> {
val typeName = "UnknownTransform"
override fun newInstance(obj: Any?) = UnknownTransform()
override fun getTypeClass(): Class<*> = UnknownTransform::class.java
}
override fun getDescribed(): Any = emptyList<Any>()
override fun params() = ""
override val name: String get() = typeName
}
class UnknownTestTransform(val a: Int, val b: Int, val c: Int) : Transform() {
companion object : DescribedTypeConstructor<UnknownTestTransform> {
val typeName = "UnknownTest"
override fun newInstance(obj: Any?) : UnknownTestTransform {
val described = obj as List<*>
return UnknownTestTransform(described[1] as Int, described[2] as Int, described[3] as Int)
}
override fun getTypeClass(): Class<*> = UnknownTransform::class.java
}
override fun getDescribed(): Any = listOf(name, a, b, c)
override fun params() = ""
override val name: String get() = typeName
}
/**
* Transform to be used on an Enumerated Type whenever a new element is added
*
* @property old The value the [new] instance should default to when not available
* @property new the value (as a String) that has been added
*/
class EnumDefaultSchemaTransform(val old: String, val new: String) : Transform() {
companion object : DescribedTypeConstructor<EnumDefaultSchemaTransform> {
/**
* Value encoded into the schema that identifies a transform as this type
*/
val typeName = "EnumDefault"
override fun newInstance(obj: Any?): EnumDefaultSchemaTransform {
val described = obj as List<*>
val old = described[1] as? String ?: throw IllegalStateException("Was expecting \"old\" as a String")
val new = described[2] as? String ?: throw IllegalStateException("Was expecting \"new\" as a String")
return EnumDefaultSchemaTransform(old, new)
}
override fun getTypeClass(): Class<*> = EnumDefaultSchemaTransform::class.java
}
@Suppress("UNUSED")
constructor (annotation: CordaSerializationTransformEnumDefault) : this(annotation.old, annotation.new)
override fun getDescribed(): Any = listOf(name, old, new)
override fun params() = "old=${old.esc()} new=${new.esc()}"
override fun equals(other: Any?) = (
(other is EnumDefaultSchemaTransform && other.new == new && other.old == old) || super.equals(other))
override fun hashCode() = (17 * new.hashCode()) + old.hashCode()
override val name: String get() = typeName
}
/**
* Transform applied to either a class or enum where a property is renamed
*
* @property from the name at time of change of the property
* @property to the new name of the property
*/
class RenameSchemaTransform(val from: String, val to: String) : Transform() {
companion object : DescribedTypeConstructor<RenameSchemaTransform> {
/**
* Value encoded into the schema that identifies a transform as this type
*/
val typeName = "Rename"
override fun newInstance(obj: Any?): RenameSchemaTransform {
val described = obj as List<*>
val from = described[1] as? String ?: throw IllegalStateException("Was expecting \"from\" as a String")
val to = described[2] as? String ?: throw IllegalStateException("Was expecting \"to\" as a String")
return RenameSchemaTransform(from, to)
}
override fun getTypeClass(): Class<*> = RenameSchemaTransform::class.java
}
@Suppress("UNUSED")
constructor (annotation: CordaSerializationTransformRename) : this(annotation.from, annotation.to)
override fun getDescribed(): Any = listOf(name, from, to)
override fun params() = "from=${from.esc()} to=${to.esc()}"
override fun equals(other: Any?) = (
(other is RenameSchemaTransform && other.from == from && other.to == to) || super.equals(other))
override fun hashCode() = (11 * from.hashCode()) + to.hashCode()
override val name: String get() = typeName
}
/**
* Represents the set of all transforms that can be a applied to all classes represented as part of
* an AMQP schema. It forms a part of the AMQP envelope alongside the [Schema] and the serialized bytes
*
* @property types maps class names to a map of transformation types. In turn those transformation types
* are each a list of instances o that transform.
*/
data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, MutableList<Transform>>>) : DescribedType {
companion object : DescribedTypeConstructor<TransformsSchema> {
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor
/**
* Prepare a schema for encoding, takes all of the types being transmitted and inspects each
* one for any transform annotations. If there are any build up a set that can be
* encoded into the AMQP [Envelope]
*
* @param schema should be a [Schema] generated for a serialised data structure
* @param sf should be provided by the same serialization context that generated the schema
*/
fun build(schema: Schema, sf: SerializerFactory): TransformsSchema {
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
schema.types.forEach { type ->
sf.transformsCache.computeIfAbsent(type.name) {
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
try {
val clazz = sf.classloader.loadClass(type.name)
supportedTransforms.forEach { transform ->
clazz.getAnnotation(transform.type)?.let { list ->
transform.getAnnotations(list).forEach { annotation ->
val t = transform.enum.build(annotation)
// we're explicitly rejecting repeated annotations, whilst it's fine and we'd just
// ignore them it feels like a good thing to alert the user to since this is
// more than likely a typo in their code so best make it an actual error
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }
.filter { t == it }.isNotEmpty()) {
throw NotSerializableException(
"Repeated unique transformation annotation of type ${t.name}")
}
transforms[transform.enum]!!.add(t)
}
}
}
} catch (_: ClassNotFoundException) {
// if we can't load the class we'll end up caching an empty list which is fine as that
// list, on lookup, won't be included in the schema because it's empty
}
transforms
}.apply {
if (isNotEmpty()) {
rtn[type.name] = this
}
}
}
return TransformsSchema(rtn)
}
override fun getTypeClass(): Class<*> = TransformsSchema::class.java
/**
* Constructs an instance of the object from the serialised form of an instance
* of this object
*/
override fun newInstance(described: Any?): TransformsSchema {
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
val describedType = described as? DescribedType ?: return TransformsSchema(rtn)
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
val map = describedType.described as? Map<*, *> ?:
throw NotSerializableException("Transform schema must be encoded as a map")
map.forEach { type ->
val fingerprint = type.key as? String ?:
throw NotSerializableException("Fingerprint must be encoded as a string")
rtn[fingerprint] = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
(type.value as Map<*, *>).forEach { transformType, transforms ->
val transform = TransformTypes.newInstance(transformType)
rtn[fingerprint]!![transform] = mutableListOf()
(transforms as List<*>).forEach {
rtn[fingerprint]!![TransformTypes.newInstance(transformType)]?.add(Transform.newInstance(it)) ?:
throw NotSerializableException("De-serialization error with transform for class "
+ "${type.key} ${transform.name}")
}
}
}
return TransformsSchema(rtn)
}
}
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = types
override fun toString(): String {
data class Indent(val indent: String) {
@Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}")
override fun toString() = indent
}
val sb = StringBuilder("")
val indent = Indent("")
sb.appendln("$indent<type-transforms>")
types.forEach { type ->
val indent = Indent(indent)
sb.appendln("$indent<type name=${type.key.esc()}>")
type.value.forEach { transform ->
val indent = Indent(indent)
sb.appendln("$indent<transforms type=${transform.key.name.esc()}>")
transform.value.forEach {
val indent = Indent(indent)
sb.appendln("$indent<transform ${it.params()} />")
}
sb.appendln("$indent</transforms>")
}
sb.appendln("$indent</type>")
}
sb.appendln("$indent</type-transforms>")
return sb.toString()
}
}
private fun String.esc() = "\"$this\""

View File

@ -2,11 +2,11 @@ package net.corda.nodeapi.internal.serialization;
import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationDefaults;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.testing.TestDependencyInjectionBase;
import net.corda.testing.SerializationEnvironmentRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.Serializable;
@ -16,13 +16,14 @@ import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
public final class ForbiddenLambdaSerializationTests extends TestDependencyInjectionBase {
public final class ForbiddenLambdaSerializationTests {
@Rule
public SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
private SerializationFactory factory;
@Before
public void setup() {
factory = SerializationDefaults.INSTANCE.getSERIALIZATION_FACTORY();
factory = testSerialization.env.getSERIALIZATION_FACTORY();
}
@Test

View File

@ -2,11 +2,11 @@ package net.corda.nodeapi.internal.serialization;
import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationDefaults;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.testing.TestDependencyInjectionBase;
import net.corda.testing.SerializationEnvironmentRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.Serializable;
@ -15,14 +15,15 @@ import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
public final class LambdaCheckpointSerializationTest extends TestDependencyInjectionBase {
public final class LambdaCheckpointSerializationTest {
@Rule
public SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
private SerializationFactory factory;
private SerializationContext context;
@Before
public void setup() {
factory = SerializationDefaults.INSTANCE.getSERIALIZATION_FACTORY();
factory = testSerialization.env.getSERIALIZATION_FACTORY();
context = new SerializationContextImpl(SerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint);
}

View File

@ -186,6 +186,8 @@ public class JavaSerializationOutputTests {
decoder.register(CompositeType.Companion.getDESCRIPTOR(), CompositeType.Companion);
decoder.register(Choice.Companion.getDESCRIPTOR(), Choice.Companion);
decoder.register(RestrictedType.Companion.getDESCRIPTOR(), RestrictedType.Companion);
decoder.register(Transform.Companion.getDESCRIPTOR(), Transform.Companion);
decoder.register(TransformsSchema.Companion.getDESCRIPTOR(), TransformsSchema.Companion);
new EncoderImpl(decoder);
decoder.setByteBuffer(ByteBuffer.wrap(bytes.getBytes(), 8, bytes.getSize() - 8));

View File

@ -11,9 +11,13 @@ import net.corda.testing.*
import net.corda.testing.node.MockServices
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
class AttachmentsClassLoaderStaticContractTests : TestDependencyInjectionBase() {
class AttachmentsClassLoaderStaticContractTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
class AttachmentDummyContract : Contract {
companion object {

View File

@ -23,6 +23,7 @@ import net.corda.testing.node.MockServices
import org.apache.commons.io.IOUtils
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -32,7 +33,7 @@ import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertFailsWith
class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
class AttachmentsClassLoaderTests {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar")
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
@ -48,6 +49,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var serviceHub: DummyServiceHub
class DummyServiceHub : MockServices() {

View File

@ -14,7 +14,7 @@ import net.corda.core.utilities.sequence
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
@ -28,7 +28,10 @@ import java.time.Instant
import java.util.Collections
import kotlin.test.*
class KryoTests : TestDependencyInjectionBase() {
class KryoTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext

View File

@ -7,19 +7,20 @@ import net.corda.node.services.statemachine.SessionData
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import net.corda.nodeapi.internal.serialization.amqp.Envelope
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.amqpSpecific
import net.corda.testing.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
import java.nio.charset.StandardCharsets.US_ASCII
import java.util.*
class ListsSerializationTest : TestDependencyInjectionBase() {
class ListsSerializationTest {
private companion object {
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
@ -31,6 +32,10 @@ class ListsSerializationTest : TestDependencyInjectionBase() {
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `check list can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(emptyList<Int>())

View File

@ -6,23 +6,28 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.amqpSpecific
import net.corda.testing.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Assert.assertArrayEquals
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.util.*
import kotlin.test.assertEquals
class MapsSerializationTest : TestDependencyInjectionBase() {
class MapsSerializationTest {
private companion object {
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
val smallMap = mapOf("foo" to "bar", "buzz" to "bull")
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `check EmptyMap serialization`() = amqpSpecific("kotlin.collections.EmptyMap is not enabled for Kryo serialization") {
assertEqualAfterRoundTripSerialization(emptyMap<Any, Any>())

View File

@ -4,8 +4,9 @@ import net.corda.core.crypto.Crypto
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@ -13,8 +14,7 @@ import java.security.PrivateKey
import kotlin.test.assertTrue
@RunWith(Parameterized::class)
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) : TestDependencyInjectionBase() {
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
@ -26,6 +26,10 @@ class PrivateKeySerializationTest(private val privateKey: PrivateKey, private va
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `passed with expected UseCases`() {
assertTrue { privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes.isNotEmpty() }

View File

@ -5,22 +5,25 @@ import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.io.Output
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.rigorousMock
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
class SerializationTokenTest : TestDependencyInjectionBase() {
class SerializationTokenTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@Before
fun setup() {
factory = SerializationDefaults.SERIALIZATION_FACTORY
context = SerializationDefaults.CHECKPOINT_CONTEXT.withWhitelisted(SingletonSerializationToken::class.java)
factory = testSerialization.env.SERIALIZATION_FACTORY
context = testSerialization.env.CHECKPOINT_CONTEXT.withWhitelisted(SingletonSerializationToken::class.java)
}
// Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized

View File

@ -5,19 +5,24 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.util.*
class SetsSerializationTest : TestDependencyInjectionBase() {
class SetsSerializationTest {
private companion object {
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `check set can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(emptySet<Int>())

View File

@ -18,18 +18,31 @@ class TestSerializationOutput(
if (verbose) println(schema)
super.writeSchema(schema, data)
}
override fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) {
if(verbose) {
println ("Writing Transform Schema")
println (transformsSchema)
}
super.writeTransformSchema(transformsSchema, data)
}
}
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
data class BytesAndSchema<T : Any>(val obj: SerializedBytes<T>, val schema: Schema)
data class BytesAndSchemas<T : Any>(
val obj: SerializedBytes<T>,
val schema: Schema,
val transformsSchema: TransformsSchema)
// Extension for the serialize routine that returns the scheme encoded into the
// bytes as well as the bytes for simple testing
@Throws(NotSerializableException::class)
fun <T : Any> SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchema<T> {
fun <T : Any> SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchemas<T> {
try {
return BytesAndSchema(_serialize(obj), Schema(schemaHistory.toList()))
val blob = _serialize(obj)
val schema = Schema(schemaHistory.toList())
return BytesAndSchemas(blob, schema, TransformsSchema.build(schema, serializerFactory))
} finally {
andFinally()
}

View File

@ -0,0 +1,436 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.*
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.net.URI
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EnumEvolvabilityTests {
var localPath = "file:///home/katelyn/srcs/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp"
companion object {
val VERBOSE = false
}
enum class NotAnnotated {
A, B, C, D
}
@CordaSerializationTransformEnumDefaults()
enum class MissingDefaults {
A, B, C, D
}
@CordaSerializationTransformRenames()
enum class MissingRenames {
A, B, C, D
}
@CordaSerializationTransformEnumDefault("D", "A")
enum class AnnotatedEnumOnce {
A, B, C, D
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault("E", "D"),
CordaSerializationTransformEnumDefault("D", "A"))
enum class AnnotatedEnumTwice {
A, B, C, D, E
}
@CordaSerializationTransformRename("E", "D")
enum class RenameEnumOnce {
A, B, C, E
}
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("E", "C"),
CordaSerializationTransformRename("F", "D"))
enum class RenameEnumTwice {
A, B, E, F
}
@Test
fun noAnnotation() {
data class C (val n: NotAnnotated)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(NotAnnotated.A))
assertEquals(2, bAndS.schema.types.size)
assertEquals(0, bAndS.transformsSchema.types.size)
}
@Test
fun missingDefaults() {
data class C (val m: MissingDefaults)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingDefaults.A))
assertEquals(2, bAndS.schema.types.size)
assertEquals(0, bAndS.transformsSchema.types.size)
}
@Test
fun missingRenames() {
data class C (val m: MissingRenames)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingRenames.A))
assertEquals(2, bAndS.schema.types.size)
assertEquals(0, bAndS.transformsSchema.types.size)
}
@Test
fun defaultAnnotationIsAddedToEnvelope() {
data class C (val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumOnce.D))
// only the enum is decorated so schema sizes should be different (2 objects, only one evolved)
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals (AnnotatedEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first())
val schema = bAndS.transformsSchema.types.values.first()
assertEquals(1, schema.size)
assertTrue (schema.keys.contains(TransformTypes.EnumDefault))
assertEquals (1, schema[TransformTypes.EnumDefault]!!.size)
assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
}
@Test
fun doubleDefaultAnnotationIsAddedToEnvelope() {
data class C (val annotatedEnum: AnnotatedEnumTwice)
val sf = testDefaultFactory()
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumTwice.E))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals (AnnotatedEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first())
val schema = bAndS.transformsSchema.types.values.first()
assertEquals(1, schema.size)
assertTrue (schema.keys.contains(TransformTypes.EnumDefault))
assertEquals (2, schema[TransformTypes.EnumDefault]!!.size)
assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
assertEquals ("E", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
assertTrue (schema[TransformTypes.EnumDefault]!![1] is EnumDefaultSchemaTransform)
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).new)
assertEquals ("A", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).old)
}
@Test
fun defaultAnnotationIsAddedToEnvelopeAndDeserialised() {
data class C (val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumOnce.D))
val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb)
// as with the serialisation stage, de-serialising the object we should see two
// types described in the header with one of those having transforms
assertEquals(2, db.envelope.schema.types.size)
assertEquals(1, db.envelope.transformsSchema.types.size)
val eName = AnnotatedEnumOnce::class.java.name
val types = db.envelope.schema.types
val transforms = db.envelope.transformsSchema.types
assertEquals(1, types.filter { it.name == eName }.size)
assertTrue(eName in transforms)
val schema = transforms[eName]
assertTrue (schema!!.keys.contains(TransformTypes.EnumDefault))
assertEquals (1, schema[TransformTypes.EnumDefault]!!.size)
assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform)
assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
}
@Test
fun doubleDefaultAnnotationIsAddedToEnvelopeAndDeserialised() {
data class C(val annotatedEnum: AnnotatedEnumTwice)
val sf = testDefaultFactory()
val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumTwice.E))
val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb)
// as with the serialisation stage, de-serialising the object we should see two
// types described in the header with one of those having transforms
assertEquals(2, db.envelope.schema.types.size)
assertEquals(1, db.envelope.transformsSchema.types.size)
val transforms = db.envelope.transformsSchema.types
assertTrue (transforms.contains(AnnotatedEnumTwice::class.java.name))
assertTrue (transforms[AnnotatedEnumTwice::class.java.name]!!.contains(TransformTypes.EnumDefault))
assertEquals (2, transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!.size)
val enumDefaults = transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!
assertEquals("E", (enumDefaults[0] as EnumDefaultSchemaTransform).new)
assertEquals("D", (enumDefaults[0] as EnumDefaultSchemaTransform).old)
assertEquals("D", (enumDefaults[1] as EnumDefaultSchemaTransform).new)
assertEquals("A", (enumDefaults[1] as EnumDefaultSchemaTransform).old)
}
@Test
fun renameAnnotationIsAdded() {
data class C (val annotatedEnum: RenameEnumOnce)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumOnce.E))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals (RenameEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first())
val serialisedSchema = bAndS.transformsSchema.types[RenameEnumOnce::class.java.name]!!
assertEquals(1, serialisedSchema.size)
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
// Now de-serialise the blob
val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj)
assertEquals(2, cAndS.envelope.schema.types.size)
assertEquals(1, cAndS.envelope.transformsSchema.types.size)
assertEquals (RenameEnumOnce::class.java.name, cAndS.envelope.transformsSchema.types.keys.first())
val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumOnce::class.java.name]!!
assertEquals(1, deserialisedSchema.size)
assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(1, deserialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
}
@Test
fun doubleRenameAnnotationIsAdded() {
data class C (val annotatedEnum: RenameEnumTwice)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumTwice.F))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals (RenameEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first())
val serialisedSchema = bAndS.transformsSchema.types[RenameEnumTwice::class.java.name]!!
assertEquals(1, serialisedSchema.size)
assertTrue(serialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(2, serialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("C", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from)
assertEquals("F", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to)
// Now de-serialise the blob
val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj)
assertEquals(2, cAndS.envelope.schema.types.size)
assertEquals(1, cAndS.envelope.transformsSchema.types.size)
assertEquals (RenameEnumTwice::class.java.name, cAndS.envelope.transformsSchema.types.keys.first())
val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumTwice::class.java.name]!!
assertEquals(1, deserialisedSchema.size)
assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename))
assertEquals(2, deserialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("C", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from)
assertEquals("F", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to)
}
@CordaSerializationTransformRename(from="A", to="X")
@CordaSerializationTransformEnumDefault(old = "X", new="E")
enum class RenameAndExtendEnum {
X, B, C, D, E
}
@Test
fun bothAnnotationTypes() {
data class C (val annotatedEnum: RenameAndExtendEnum)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameAndExtendEnum.X))
assertEquals(2, bAndS.schema.types.size)
assertEquals(1, bAndS.transformsSchema.types.size)
assertEquals (RenameAndExtendEnum::class.java.name, bAndS.transformsSchema.types.keys.first())
val serialisedSchema = bAndS.transformsSchema.types[RenameAndExtendEnum::class.java.name]!!
// This time there should be two distinct transform types (all previous tests have had only
// a single type
assertEquals(2, serialisedSchema.size)
assertTrue (serialisedSchema.containsKey(TransformTypes.Rename))
assertTrue (serialisedSchema.containsKey(TransformTypes.EnumDefault))
assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size)
assertEquals("A", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from)
assertEquals("X", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
assertEquals(1, serialisedSchema[TransformTypes.EnumDefault]!!.size)
assertEquals("E", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new)
assertEquals("X", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old)
}
@CordaSerializationTransformEnumDefaults (
CordaSerializationTransformEnumDefault("D", "A"),
CordaSerializationTransformEnumDefault("D", "A"))
enum class RepeatedAnnotation {
A, B, C, D, E
}
@Test
fun repeatedAnnotation() {
data class C (val a: RepeatedAnnotation)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RepeatedAnnotation.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault("D", "A")
enum class E1 {
A, B, C, D
}
@CordaSerializationTransformEnumDefaults (
CordaSerializationTransformEnumDefault("D", "A"),
CordaSerializationTransformEnumDefault("E", "A"))
enum class E2 {
A, B, C, D, E
}
@CordaSerializationTransformEnumDefaults (CordaSerializationTransformEnumDefault("D", "A"))
enum class E3 {
A, B, C, D
}
@Test
fun multiEnums() {
data class A (val a: E1, val b: E2)
data class B (val a: E3, val b: A, val c: E1)
data class C (val a: B, val b: E2, val c: E3)
val c = C(B(E3.A,A(E1.A,E2.B),E1.C),E2.B,E3.A)
val sf = testDefaultFactory()
// Serialise the object
val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
println (bAndS.transformsSchema)
// we have six types and three of those, the enums, should have transforms
assertEquals(6, bAndS.schema.types.size)
assertEquals(3, bAndS.transformsSchema.types.size)
assertTrue (E1::class.java.name in bAndS.transformsSchema.types)
assertTrue (E2::class.java.name in bAndS.transformsSchema.types)
assertTrue (E3::class.java.name in bAndS.transformsSchema.types)
val e1S = bAndS.transformsSchema.types[E1::class.java.name]!!
val e2S = bAndS.transformsSchema.types[E2::class.java.name]!!
val e3S = bAndS.transformsSchema.types[E3::class.java.name]!!
assertEquals(1, e1S.size)
assertEquals(1, e2S.size)
assertEquals(1, e3S.size)
assertTrue(TransformTypes.EnumDefault in e1S)
assertTrue(TransformTypes.EnumDefault in e2S)
assertTrue(TransformTypes.EnumDefault in e3S)
assertEquals(1, e1S[TransformTypes.EnumDefault]!!.size)
assertEquals(2, e2S[TransformTypes.EnumDefault]!!.size)
assertEquals(1, e3S[TransformTypes.EnumDefault]!!.size)
}
@Test
fun testCache() {
data class C2(val annotatedEnum: AnnotatedEnumOnce)
data class C1(val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
assertEquals(0, sf.transformsCache.size)
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
assertEquals(2, sf.transformsCache.size)
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
assertEquals(3, sf.transformsCache.size)
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
assertTrue(sf.transformsCache.containsKey(C2::class.java.name))
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals (sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])
}
//@UnknownTransformAnnotation (10, 20, 30)
enum class WithUnknownTest {
A, B, C, D
}
data class WrapsUnknown(val unknown: WithUnknownTest)
// To regenerate the types for this test uncomment the UnknownTransformAnnotation from
// TransformTypes.kt and SupportedTransforms.kt
// ALSO: remember to re-annotate the enum WithUnkownTest above
@Test
fun testUnknownTransform() {
val resource = "EnumEvolvabilityTests.testUnknownTransform"
val sf = testDefaultFactory()
//File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(WrapsUnknown(WithUnknownTest.D)).bytes)
val path = EvolvabilityTests::class.java.getResource(resource)
val sb1 = File(path.toURI()).readBytes()
val envelope = DeserializationInput(sf).deserializeAndReturnEnvelope(SerializedBytes<WrapsUnknown>(sb1)).envelope
assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name))
assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown))
}
}

View File

@ -5,36 +5,40 @@ import net.corda.core.serialization.SerializedBytes
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.net.URI
import kotlin.test.assertEquals
// To regenerate any of the binary test files do the following
//
// 0. set localPath accordingly
// 1. Uncomment the code where the original form of the class is defined in the test
// 2. Comment out the rest of the test
// 3. Run the test
// 4. Using the printed path copy that file to the resources directory
// 5. Comment back out the generation code and uncomment the actual test
class EvolvabilityTests {
// When regenerating the test files this needs to be set to the file system location of the resource files
var localPath = "file:///<path>/<to>/<toplevel of>/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp"
@Test
fun simpleOrderSwapSameType() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapSameType")
val f = File(path.toURI())
val resource= "EvolvabilityTests.simpleOrderSwapSameType"
val A = 1
val B = 2
// Original version of the class for the serialised version of this class
//
// data class C (val a: Int, val b: Int)
// val sc = SerializationOutput(sf).serialize(C(A, B))
// f.writeBytes(sc.bytes)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes)
// println (path)
// new version of the class, in this case the order of the parameters has been swapped
data class C(val b: Int, val a: Int)
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
@ -45,21 +49,20 @@ class EvolvabilityTests {
@Test
fun simpleOrderSwapDifferentType() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapDifferentType")
val f = File(path.toURI())
val A = 1
val B = "two"
val resource = "EvolvabilityTests.simpleOrderSwapDifferentType"
// Original version of the class as it was serialised
//
// data class C (val a: Int, val b: String)
// val sc = SerializationOutput(sf).serialize(C(A, B))
// f.writeBytes(sc.bytes)
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes)
// println (path)
// new version of the class, in this case the order of the parameters has been swapped
data class C(val b: String, val a: Int)
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
@ -70,24 +73,24 @@ class EvolvabilityTests {
@Test
fun addAdditionalParamNotMandatory() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAdditionalParamNotMandatory")
val f = File(path.toURI())
val A = 1
val resource = "EvolvabilityTests.addAdditionalParamNotMandatory"
// Original version of the class as it was serialised
//
// data class C(val a: Int)
// val sc = SerializationOutput(sf).serialize(C(A))
// f.writeBytes(sc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes( SerializationOutput(sf).serialize(C(A))
.bytes)
data class C(val a: Int, val b: Int?)
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(A, deserializedC.a)
assertEquals(null, deserializedC.b)
}
}
@Test(expected = NotSerializableException::class)
fun addAdditionalParam() {
@ -119,22 +122,20 @@ class EvolvabilityTests {
@Test
fun removeParameters() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters")
val f = File(path.toURI())
val resource = "EvolvabilityTests.removeParameters"
val A = 1
val B = "two"
val C = "three"
val D = 4
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C, D))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes)
data class CC(val b: String, val d: Int)
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters")
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
@ -146,23 +147,22 @@ class EvolvabilityTests {
@Test
fun addAndRemoveParameters() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAndRemoveParameters")
val f = File(path.toURI())
val A = 1
val B = "two"
val C = "three"
val D = 4
val E = null
val resource = "EvolvabilityTests.addAndRemoveParameters"
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C, D))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes)
data class CC(val a: Int, val e: Boolean?, val d: Int)
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
@ -174,16 +174,12 @@ class EvolvabilityTests {
@Test
fun addMandatoryFieldWithAltConstructor() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addMandatoryFieldWithAltConstructor")
val f = File(path.toURI())
val A = 1
val resource = "EvolvabilityTests.addMandatoryFieldWithAltConstructor"
// Original version of the class as it was serialised
//
// data class CC(val a: Int)
// val scc = SerializationOutput(sf).serialize(CC(A))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A)).bytes)
@Suppress("UNUSED")
data class CC(val a: Int, val b: String) {
@ -191,6 +187,8 @@ class EvolvabilityTests {
constructor (a: Int) : this(a, "hello")
}
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
@ -228,19 +226,14 @@ class EvolvabilityTests {
@Test
fun addMandatoryFieldWithAltReorderedConstructor() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource(
"EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor")
val f = File(path.toURI())
val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor"
val A = 1
val B = 100
val C = "This is not a banana"
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: Int, val c: String)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes)
@Suppress("UNUSED")
data class CC(val a: Int, val b: Int, val c: String, val d: String) {
@ -250,6 +243,8 @@ class EvolvabilityTests {
constructor (c: String, a: Int, b: Int) : this(a, b, c, "wibble")
}
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
@ -262,20 +257,15 @@ class EvolvabilityTests {
@Test
fun addMandatoryFieldWithAltReorderedConstructorAndRemoval() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource(
"EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval")
val f = File(path.toURI())
val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval"
val A = 1
@Suppress("UNUSED_VARIABLE")
val B = 100
val C = "This is not a banana"
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: Int, val c: String)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes)
// b is removed, d is added
data class CC(val a: Int, val c: String, val d: String) {
@ -286,6 +276,8 @@ class EvolvabilityTests {
constructor (c: String, a: Int) : this(a, c, "wibble")
}
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
@ -297,10 +289,9 @@ class EvolvabilityTests {
@Test
fun multiVersion() {
val sf = testDefaultFactory()
val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.1")
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2")
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3")
val resource1 = "EvolvabilityTests.multiVersion.1"
val resource2 = "EvolvabilityTests.multiVersion.2"
val resource3 = "EvolvabilityTests.multiVersion.3"
val a = 100
val b = 200
val c = 300
@ -310,24 +301,15 @@ class EvolvabilityTests {
//
// Version 1:
// data class C (val a: Int, val b: Int)
//
// val scc = SerializationOutput(sf).serialize(C(a, b))
// File(path1.toURI()).writeBytes(scc.bytes)
// println ("Path = $path1")
// File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b)).bytes)
//
// Version 2 - add param c
// data class C (val c: Int, val b: Int, val a: Int)
//
// val scc = SerializationOutput(sf).serialize(C(c, b, a))
// File(path2.toURI()).writeBytes(scc.bytes)
// println ("Path = $path2")
// File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(c, b, a)).bytes)
//
// Version 3 - add param d
// data class C (val b: Int, val c: Int, val d: Int, val a: Int)
//
// val scc = SerializationOutput(sf).serialize(C(b, c, d, a))
// File(path3.toURI()).writeBytes(scc.bytes)
// println ("Path = $path3")
// File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, a)).bytes)
@Suppress("UNUSED")
data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
@ -341,6 +323,10 @@ class EvolvabilityTests {
constructor (a: Int, b: Int, c: Int, d: Int) : this(-1, c, b, a, d)
}
val path1 = EvolvabilityTests::class.java.getResource(resource1)
val path2 = EvolvabilityTests::class.java.getResource(resource2)
val path3 = EvolvabilityTests::class.java.getResource(resource3)
val sb1 = File(path1.toURI()).readBytes()
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
@ -372,24 +358,21 @@ class EvolvabilityTests {
@Test
fun changeSubType() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.changeSubType")
val f = File(path.toURI())
val resource = "EvolvabilityTests.changeSubType"
val oa = 100
val ia = 200
// Original version of the class as it was serialised
//
// data class Inner (val a: Int)
// data class Outer (val a: Int, val b: Inner)
// val scc = SerializationOutput(sf).serialize(Outer(oa, Inner (ia)))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(Outer(oa, Inner (ia))).bytes)
// Add a parameter to inner but keep outer unchanged
data class Inner(val a: Int, val b: String?)
data class Outer(val a: Int, val b: Inner)
val path = EvolvabilityTests::class.java.getResource(resource)
val f = File(path.toURI())
val sc2 = f.readBytes()
val outer = DeserializationInput(sf).deserialize(SerializedBytes<Outer>(sc2))
@ -401,9 +384,10 @@ class EvolvabilityTests {
@Test
fun multiVersionWithRemoval() {
val sf = testDefaultFactory()
val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.1")
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.2")
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.3")
val resource1 = "EvolvabilityTests.multiVersionWithRemoval.1"
val resource2 = "EvolvabilityTests.multiVersionWithRemoval.2"
val resource3 = "EvolvabilityTests.multiVersionWithRemoval.3"
@Suppress("UNUSED_VARIABLE")
val a = 100
@ -417,24 +401,15 @@ class EvolvabilityTests {
//
// Version 1:
// data class C (val a: Int, val b: Int, val c: Int)
// File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b, c)).bytes)
//
// val scc = SerializationOutput(sf).serialize(C(a, b, c))
// File(path1.toURI()).writeBytes(scc.bytes)
// println ("Path = $path1")
//
// Version 2 - add param c
// Version 2 - remove property a, add property e
// data class C (val b: Int, val c: Int, val d: Int, val e: Int)
//
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e))
// File(path2.toURI()).writeBytes(scc.bytes)
// println ("Path = $path2")
// File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e)).bytes)
//
// Version 3 - add param d
// data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int)
//
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f))
// File(path3.toURI()).writeBytes(scc.bytes)
// println ("Path = $path3")
// File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e, f)).bytes)
@Suppress("UNUSED")
data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
@ -451,6 +426,10 @@ class EvolvabilityTests {
constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this(b, c, d, e, f, -1)
}
val path1 = EvolvabilityTests::class.java.getResource(resource1)
val path2 = EvolvabilityTests::class.java.getResource(resource2)
val path3 = EvolvabilityTests::class.java.getResource(resource3)
val sb1 = File(path1.toURI()).readBytes()
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))

View File

@ -164,6 +164,8 @@ class SerializationOutputTests {
this.register(Choice.DESCRIPTOR, Choice.Companion)
this.register(RestrictedType.DESCRIPTOR, RestrictedType.Companion)
this.register(ReferencedObject.DESCRIPTOR, ReferencedObject.Companion)
this.register(TransformsSchema.DESCRIPTOR, TransformsSchema.Companion)
this.register(TransformTypes.DESCRIPTOR, TransformTypes.Companion)
}
EncoderImpl(decoder)
decoder.setByteBuffer(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8))
@ -430,9 +432,7 @@ class SerializationOutputTests {
private fun serdesThrowableWithInternalInfo(t: Throwable, factory: SerializerFactory, factory2: SerializerFactory, expectedEqual: Boolean = true): Throwable = withTestSerialization {
val newContext = SerializationFactory.defaultFactory.defaultContext.withProperty(CommonPropertyNames.IncludeInternalInfo, true)
val deserializedObj = SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
return deserializedObj
SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
}
@Test