mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
CORDA-943 - Deterministic serialization order breaks object ref cache
This commit is contained in:
parent
cf37895327
commit
e5627622db
@ -1,7 +1,6 @@
|
|||||||
package net.corda.core.contracts
|
package net.corda.core.contracts
|
||||||
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
|
@ -18,7 +18,7 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer<Any> {
|
|||||||
override fun writeClassInfo(output: SerializationOutput) {
|
override fun writeClassInfo(output: SerializationOutput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
if (obj is ByteArray) {
|
if (obj is ByteArray) {
|
||||||
data.putObject(Binary(obj))
|
data.putObject(Binary(obj))
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,7 +30,7 @@ interface AMQPSerializer<out T> {
|
|||||||
/**
|
/**
|
||||||
* Write the given object, with declared type, to the output.
|
* Write the given object, with declared type, to the output.
|
||||||
*/
|
*/
|
||||||
fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput)
|
fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int = 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the given object from the input. The envelope is provided in case the schema is required.
|
* Read the given object from the input. The envelope is provided in case the schema is required.
|
||||||
|
@ -45,12 +45,12 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
// Write described
|
// Write described
|
||||||
data.withDescribed(typeNotation.descriptor) {
|
data.withDescribed(typeNotation.descriptor) {
|
||||||
withList {
|
withList {
|
||||||
for (entry in obj as Array<*>) {
|
for (entry in obj as Array<*>) {
|
||||||
output.writeObjectOrNull(entry, this, elementType)
|
output.writeObjectOrNull(entry, this, elementType, debugIndent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,15 +109,19 @@ abstract class PrimArraySerializer(type: Type, factory: SerializerFactory) : Arr
|
|||||||
|
|
||||||
class PrimIntArraySerializer(factory: SerializerFactory) :
|
class PrimIntArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(IntArray::class.java, factory) {
|
PrimArraySerializer(IntArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) {
|
||||||
|
(obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimCharArraySerializer(factory: SerializerFactory) :
|
class PrimCharArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(CharArray::class.java, factory) {
|
PrimArraySerializer(CharArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as CharArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) { (obj as CharArray).forEach {
|
||||||
|
output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T> List<T>.toArrayOfType(type: Type): Any {
|
override fun <T> List<T>.toArrayOfType(type: Type): Any {
|
||||||
@ -132,35 +136,45 @@ class PrimCharArraySerializer(factory: SerializerFactory) :
|
|||||||
|
|
||||||
class PrimBooleanArraySerializer(factory: SerializerFactory) :
|
class PrimBooleanArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(BooleanArray::class.java, factory) {
|
PrimArraySerializer(BooleanArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) {
|
||||||
|
(obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimDoubleArraySerializer(factory: SerializerFactory) :
|
class PrimDoubleArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(DoubleArray::class.java, factory) {
|
PrimArraySerializer(DoubleArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) {
|
||||||
|
(obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimFloatArraySerializer(factory: SerializerFactory) :
|
class PrimFloatArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(FloatArray::class.java, factory) {
|
PrimArraySerializer(FloatArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) {
|
||||||
|
(obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimShortArraySerializer(factory: SerializerFactory) :
|
class PrimShortArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(ShortArray::class.java, factory) {
|
PrimArraySerializer(ShortArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) {
|
||||||
|
(obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimLongArraySerializer(factory: SerializerFactory) :
|
class PrimLongArraySerializer(factory: SerializerFactory) :
|
||||||
PrimArraySerializer(LongArray::class.java, factory) {
|
PrimArraySerializer(LongArray::class.java, factory) {
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
localWriteObject(data) { (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
|
localWriteObject(data) {
|
||||||
|
(obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,18 +66,26 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
|
override fun writeObject(
|
||||||
|
obj: Any,
|
||||||
|
data: Data,
|
||||||
|
type: Type,
|
||||||
|
output: SerializationOutput,
|
||||||
|
debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) {
|
||||||
// Write described
|
// Write described
|
||||||
data.withDescribed(typeNotation.descriptor) {
|
data.withDescribed(typeNotation.descriptor) {
|
||||||
withList {
|
withList {
|
||||||
for (entry in obj as Collection<*>) {
|
for (entry in obj as Collection<*>) {
|
||||||
output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0])
|
output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], debugIndent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
|
override fun readObject(
|
||||||
|
obj: Any,
|
||||||
|
schemas: SerializationSchemas,
|
||||||
|
input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
|
||||||
// TODO: Can we verify the entries in the list?
|
// 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]) })
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ class CorDappCustomSerializer(
|
|||||||
|
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
val proxy = uncheckedCast<SerializationCustomSerializer<*, *>,
|
val proxy = uncheckedCast<SerializationCustomSerializer<*, *>,
|
||||||
SerializationCustomSerializer<Any?, Any?>>(serializer).toProxy(obj)
|
SerializationCustomSerializer<Any?, Any?>>(serializer).toProxy(obj)
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
|
|||||||
*/
|
*/
|
||||||
override val revealSubclassesInSchema: Boolean get() = false
|
override val revealSubclassesInSchema: Boolean get() = false
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
data.withDescribed(descriptor) {
|
data.withDescribed(descriptor) {
|
||||||
writeDescribedObject(uncheckedCast(obj), data, type, output)
|
writeDescribedObject(uncheckedCast(obj), data, type, output)
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,7 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>): T =
|
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>): T = deserialize(bytes, T::class.java)
|
||||||
deserialize(bytes, T::class.java)
|
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
|
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
|
||||||
@ -106,11 +105,11 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)), envelope)
|
ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)), envelope)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type): Any? {
|
internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, offset: Int = 0): Any? {
|
||||||
return if (obj == null) null else readObject(obj, schema, type)
|
return if (obj == null) null else readObject(obj, schema, type, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type): Any =
|
internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, debugIndent: Int = 0): Any =
|
||||||
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
|
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
|
||||||
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
|
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
|
||||||
val objectIndex = (obj.described as UnsignedInteger).toInt()
|
val objectIndex = (obj.described as UnsignedInteger).toInt()
|
||||||
@ -119,8 +118,11 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
"is outside of the bounds for the list of size: ${objectHistory.size}")
|
"is outside of the bounds for the list of size: ${objectHistory.size}")
|
||||||
|
|
||||||
val objectRetrieved = objectHistory[objectIndex]
|
val objectRetrieved = objectHistory[objectIndex]
|
||||||
if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!))
|
if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) {
|
||||||
throw NotSerializableException("Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}'")
|
throw NotSerializableException(
|
||||||
|
"Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " +
|
||||||
|
"@ ${objectIndex}")
|
||||||
|
}
|
||||||
objectRetrieved
|
objectRetrieved
|
||||||
} else {
|
} else {
|
||||||
val objectRead = when (obj) {
|
val objectRead = when (obj) {
|
||||||
@ -138,7 +140,9 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
|
|
||||||
// Store the reference in case we need it later on.
|
// Store the reference in case we need it later on.
|
||||||
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
|
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
|
||||||
if (suitableForObjectReference(objectRead.javaClass)) objectHistory.add(objectRead)
|
if (suitableForObjectReference(objectRead.javaClass)) {
|
||||||
|
objectHistory.add(objectRead)
|
||||||
|
}
|
||||||
objectRead
|
objectRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ class EnumEvolutionSerializer(
|
|||||||
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
|
|||||||
return fromOrd
|
return fromOrd
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't")
|
if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't")
|
||||||
|
|
||||||
data.withDescribed(typeNotation.descriptor) {
|
data.withDescribed(typeNotation.descriptor) {
|
||||||
|
@ -9,15 +9,24 @@ import kotlin.reflect.KFunction
|
|||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.jvm.javaType
|
import kotlin.reflect.jvm.javaType
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializer for deserializing objects whose definition has changed since they
|
* Serializer for deserializing objects whose definition has changed since they
|
||||||
* were serialised.
|
* were serialised.
|
||||||
|
*
|
||||||
|
* @property oldReaders A linked map representing the properties of the object as they were serialized. Note
|
||||||
|
* this may contain properties that are no longer needed by the class. These *must* be read however to ensure
|
||||||
|
* any refferenced objects in the object stream are captured properly
|
||||||
|
* @property kotlinConstructor
|
||||||
|
* @property constructorArgs used to hold the properties as sent to the object's constructor. Passed in as a
|
||||||
|
* pre populated array as properties not present on the old constructor must be initialised in the factory
|
||||||
*/
|
*/
|
||||||
class EvolutionSerializer(
|
class EvolutionSerializer(
|
||||||
clazz: Type,
|
clazz: Type,
|
||||||
factory: SerializerFactory,
|
factory: SerializerFactory,
|
||||||
val readers: List<OldParam?>,
|
private val oldReaders: Map<String, OldParam>,
|
||||||
override val kotlinConstructor: KFunction<Any>?) : ObjectSerializer(clazz, factory) {
|
override val kotlinConstructor: KFunction<Any>?,
|
||||||
|
private val constructorArgs: Array<Any?>) : ObjectSerializer(clazz, factory) {
|
||||||
|
|
||||||
// explicitly set as empty to indicate it's unused by this type of serializer
|
// explicitly set as empty to indicate it's unused by this type of serializer
|
||||||
override val propertySerializers = PropertySerializersEvolution()
|
override val propertySerializers = PropertySerializersEvolution()
|
||||||
@ -27,13 +36,17 @@ class EvolutionSerializer(
|
|||||||
* when it was serialised and NOT how that class appears now
|
* when it was serialised and NOT how that class appears now
|
||||||
*
|
*
|
||||||
* @param type The jvm type of the parameter
|
* @param type The jvm type of the parameter
|
||||||
* @param idx where in the parameter list this parameter falls. Required as the parameter
|
* @param resultsIndex index into the constructor argument list where the read property
|
||||||
* order may have been changed and we need to know where into the list to look
|
* should be placed
|
||||||
* @param property object to read the actual property value
|
* @param property object to read the actual property value
|
||||||
*/
|
*/
|
||||||
data class OldParam(val type: Type, val idx: Int, val property: PropertySerializer) {
|
data class OldParam(val type: Type, var resultsIndex: Int, val property: PropertySerializer) {
|
||||||
fun readProperty(paramValues: List<*>, schemas: SerializationSchemas, input: DeserializationInput) =
|
fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, new: Array<Any?>) =
|
||||||
property.readProperty(paramValues[idx], schemas, input)
|
property.readProperty(obj, schemas, input).apply {
|
||||||
|
if(resultsIndex >= 0) {
|
||||||
|
new[resultsIndex] = this
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -47,11 +60,11 @@ class EvolutionSerializer(
|
|||||||
* TODO: Type evolution
|
* TODO: Type evolution
|
||||||
* TODO: rename annotation
|
* TODO: rename annotation
|
||||||
*/
|
*/
|
||||||
private fun getEvolverConstructor(type: Type, oldArgs: Map<String?, Type>): KFunction<Any>? {
|
private fun getEvolverConstructor(type: Type, oldArgs: Map<String, OldParam>): KFunction<Any>? {
|
||||||
val clazz: Class<*> = type.asClass()!!
|
val clazz: Class<*> = type.asClass()!!
|
||||||
if (!isConcrete(clazz)) return null
|
if (!isConcrete(clazz)) return null
|
||||||
|
|
||||||
val oldArgumentSet = oldArgs.map { Pair(it.key, it.value) }
|
val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.type) }
|
||||||
|
|
||||||
var maxConstructorVersion = Integer.MIN_VALUE
|
var maxConstructorVersion = Integer.MIN_VALUE
|
||||||
var constructor: KFunction<Any>? = null
|
var constructor: KFunction<Any>? = null
|
||||||
@ -83,34 +96,42 @@ class EvolutionSerializer(
|
|||||||
fun make(old: CompositeType, new: ObjectSerializer,
|
fun make(old: CompositeType, new: ObjectSerializer,
|
||||||
factory: SerializerFactory): AMQPSerializer<Any> {
|
factory: SerializerFactory): AMQPSerializer<Any> {
|
||||||
|
|
||||||
val oldFieldToType = old.fields.map {
|
val readersAsSerialized = linkedMapOf<String, OldParam>(
|
||||||
it.name as String? to it.getTypeAsClass(factory.classloader) as Type
|
*(old.fields.map {
|
||||||
}.toMap()
|
val returnType = try {
|
||||||
|
it.getTypeAsClass(factory.classloader)
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
throw NotSerializableException(e.message)
|
||||||
|
}
|
||||||
|
|
||||||
val constructor = getEvolverConstructor(new.type, oldFieldToType) ?:
|
it.name to OldParam(
|
||||||
|
returnType,
|
||||||
|
-1,
|
||||||
|
PropertySerializer.make(
|
||||||
|
it.name, PublicPropertyReader(null), returnType, factory))
|
||||||
|
}.toTypedArray())
|
||||||
|
)
|
||||||
|
|
||||||
|
val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?:
|
||||||
throw NotSerializableException(
|
throw NotSerializableException(
|
||||||
"Attempt to deserialize an interface: ${new.type}. Serialized form is invalid.")
|
"Attempt to deserialize an interface: ${new.type}. Serialized form is invalid.")
|
||||||
|
|
||||||
val oldArgs = mutableMapOf<String, OldParam>()
|
val constructorArgs = arrayOfNulls<Any?>(constructor.parameters.size)
|
||||||
var idx = 0
|
|
||||||
old.fields.forEach {
|
|
||||||
val returnType = it.getTypeAsClass(factory.classloader)
|
|
||||||
oldArgs[it.name] = OldParam(
|
|
||||||
returnType, idx++, PropertySerializer.make(it.name, PublicPropertyReader(null), returnType, factory))
|
|
||||||
}
|
|
||||||
|
|
||||||
val readers = constructor.parameters.map {
|
constructor.parameters.withIndex().forEach {
|
||||||
oldArgs[it.name!!] ?: if (!it.type.isMarkedNullable) {
|
readersAsSerialized.get(it.value.name!!)?.apply {
|
||||||
|
this.resultsIndex = it.index
|
||||||
|
} ?: if (!it.value.type.isMarkedNullable) {
|
||||||
throw NotSerializableException(
|
throw NotSerializableException(
|
||||||
"New parameter ${it.name} is mandatory, should be nullable for evolution to worK")
|
"New parameter ${it.value.name} is mandatory, should be nullable for evolution to worK")
|
||||||
} else null
|
|
||||||
}
|
|
||||||
|
|
||||||
return EvolutionSerializer(new.type, factory, readers, constructor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
return EvolutionSerializer(new.type, factory, readersAsSerialized, constructor, constructorArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
|
||||||
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +147,11 @@ class EvolutionSerializer(
|
|||||||
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
|
||||||
if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj")
|
if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj")
|
||||||
|
|
||||||
return construct(readers.map { it?.readProperty(obj, schemas, input) })
|
// *must* read all the parameters in the order they were serialized
|
||||||
|
oldReaders.values.zip(obj).map { it.first.readProperty(it.second, schemas, input, constructorArgs) }
|
||||||
|
|
||||||
|
return javaConstructor?.newInstance(*(constructorArgs)) ?:
|
||||||
|
throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,12 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
|
override fun writeObject(
|
||||||
|
obj: Any,
|
||||||
|
data: Data,
|
||||||
|
type: Type,
|
||||||
|
output: SerializationOutput,
|
||||||
|
debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) {
|
||||||
obj.javaClass.checkSupportedMapType()
|
obj.javaClass.checkSupportedMapType()
|
||||||
// Write described
|
// Write described
|
||||||
data.withDescribed(typeNotation.descriptor) {
|
data.withDescribed(typeNotation.descriptor) {
|
||||||
@ -81,8 +86,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
|
|||||||
data.putMap()
|
data.putMap()
|
||||||
data.enter()
|
data.enter()
|
||||||
for ((key, value) in obj as Map<*, *>) {
|
for ((key, value) in obj as Map<*, *>) {
|
||||||
output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0])
|
output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], debugIndent)
|
||||||
output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1])
|
output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], debugIndent)
|
||||||
}
|
}
|
||||||
data.exit() // exit map
|
data.exit() // exit map
|
||||||
}
|
}
|
||||||
|
@ -52,13 +52,18 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ clazz.typeName }) {
|
override fun writeObject(
|
||||||
|
obj: Any,
|
||||||
|
data: Data,
|
||||||
|
type: Type,
|
||||||
|
output: SerializationOutput,
|
||||||
|
debugIndent: Int) = ifThrowsAppend({ clazz.typeName }) {
|
||||||
// Write described
|
// Write described
|
||||||
data.withDescribed(typeNotation.descriptor) {
|
data.withDescribed(typeNotation.descriptor) {
|
||||||
// Write list
|
// Write list
|
||||||
withList {
|
withList {
|
||||||
propertySerializers.serializationOrder.forEach { property ->
|
propertySerializers.serializationOrder.forEach { property ->
|
||||||
property.getter.writeProperty(obj, this, output)
|
property.getter.writeProperty(obj, this, output, debugIndent+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import java.lang.reflect.Type
|
|||||||
*/
|
*/
|
||||||
sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) {
|
sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) {
|
||||||
abstract fun writeClassInfo(output: SerializationOutput)
|
abstract fun writeClassInfo(output: SerializationOutput)
|
||||||
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int = 0)
|
||||||
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
||||||
|
|
||||||
val type: String = generateType()
|
val type: String = generateType()
|
||||||
@ -80,8 +80,8 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
|
|||||||
input.readObjectOrNull(obj, schemas, resolvedType)
|
input.readObjectOrNull(obj, schemas, resolvedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) = ifThrowsAppend({ nameForDebug }) {
|
||||||
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType)
|
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, debugIndent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val nameForDebug = "$name(${resolvedType.typeName})"
|
private val nameForDebug = "$name(${resolvedType.typeName})"
|
||||||
@ -100,7 +100,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
|
|||||||
return if (obj is Binary) obj.array else obj
|
return if (obj is Binary) obj.array else obj
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) {
|
||||||
val value = propertyReader.read(obj)
|
val value = propertyReader.read(obj)
|
||||||
if (value is ByteArray) {
|
if (value is ByteArray) {
|
||||||
data.putObject(Binary(value))
|
data.putObject(Binary(value))
|
||||||
@ -123,7 +123,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
|
|||||||
return if (obj == null) null else (obj as Short).toChar()
|
return if (obj == null) null else (obj as Short).toChar()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) {
|
||||||
val input = propertyReader.read(obj)
|
val input = propertyReader.read(obj)
|
||||||
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
|
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
|
||||||
}
|
}
|
||||||
|
@ -349,14 +349,16 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact
|
|||||||
// This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively
|
// This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively
|
||||||
// creating a unique string for a type which we then hash in the calling function above.
|
// creating a unique string for a type which we then hash in the calling function above.
|
||||||
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
|
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
|
||||||
hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher {
|
hasher: Hasher, factory: SerializerFactory, debugIndent: Int = 1): Hasher {
|
||||||
|
|
||||||
// We don't include Example<?> and Example<T> where type is ? or T in this otherwise we
|
// We don't include Example<?> and Example<T> where type is ? or T in this otherwise we
|
||||||
// generate different fingerprints for class Outer<T>(val a: Inner<T>) when serialising
|
// generate different fingerprints for class Outer<T>(val a: Inner<T>) when serialising
|
||||||
// and deserializing (assuming deserialization is occurring in a factory that didn't
|
// and deserializing (assuming deserialization is occurring in a factory that didn't
|
||||||
// serialise the object in the first place (and thus the cache lookup fails). This is also
|
// serialise the object in the first place (and thus the cache lookup fails). This is also
|
||||||
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
|
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
|
||||||
return if (type in alreadySeen && (type !is SerializerFactory.AnyType) && (type !is TypeVariable<*>)) {
|
return if ((type in alreadySeen)
|
||||||
|
&& (type !is SerializerFactory.AnyType)
|
||||||
|
&& (type !is TypeVariable<*>)
|
||||||
|
&& (type !is WildcardType)) {
|
||||||
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
||||||
} else {
|
} else {
|
||||||
alreadySeen += type
|
alreadySeen += type
|
||||||
@ -370,29 +372,35 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
|
|||||||
hasher.putUnencodedChars(clazz.name)
|
hasher.putUnencodedChars(clazz.name)
|
||||||
} else {
|
} else {
|
||||||
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
|
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
|
||||||
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
|
fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... and concatenate the type data for each parameter type.
|
// ... and concatenate the type data for each parameter type.
|
||||||
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
||||||
fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4)
|
fingerprintForType(paramType, type, alreadySeen, orig, factory, debugIndent+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when
|
// Previously, we drew a distinction between TypeVariable, WildcardType, and AnyType, changing
|
||||||
// looking at A and B from Example<A, B> (remember we call this function recursively). When
|
// the signature of the fingerprinted object. This, however, doesn't work as it breaks bi-
|
||||||
// serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but
|
// directional fingerprints. That is, fingerprinting a concrete instance of a generic
|
||||||
// when deserializing we only have the wildcard placeholder ?, or AnyType
|
// type (Example<Int>), creates a different fingerprint from the generic type itself (Example<T>)
|
||||||
//
|
//
|
||||||
// Note, TypeVariable<*> used to be encoded as TYPE_VARIABLE_HASH but that again produces a
|
// On serialization Example<Int> is treated as Example<T>, a TypeVariable
|
||||||
// differing fingerprint on serialisation and deserialization
|
// On deserialisation it is seen as Example<?>, A WildcardType *and* a TypeVariable
|
||||||
|
// Note: AnyType is a special case of WildcardType used in other parts of the
|
||||||
|
// serializer so both cases need to be dealt with here
|
||||||
|
//
|
||||||
|
// If we treat these types as fundamentally different and alter the fingerprint we will
|
||||||
|
// end up breaking into the evolver when we shouldn't or, worse, evoking the carpenter.
|
||||||
is SerializerFactory.AnyType,
|
is SerializerFactory.AnyType,
|
||||||
|
is WildcardType,
|
||||||
is TypeVariable<*> -> {
|
is TypeVariable<*> -> {
|
||||||
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
|
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
|
||||||
}
|
}
|
||||||
is Class<*> -> {
|
is Class<*> -> {
|
||||||
if (type.isArray) {
|
if (type.isArray) {
|
||||||
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4)
|
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, debugIndent+1)
|
||||||
.putUnencodedChars(ARRAY_HASH)
|
.putUnencodedChars(ARRAY_HASH)
|
||||||
} else if (SerializerFactory.isPrimitive(type)) {
|
} else if (SerializerFactory.isPrimitive(type)) {
|
||||||
hasher.putUnencodedChars(type.name)
|
hasher.putUnencodedChars(type.name)
|
||||||
@ -412,17 +420,15 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
|
|||||||
// to the CorDapp but maybe reference to the JAR in the short term.
|
// to the CorDapp but maybe reference to the JAR in the short term.
|
||||||
hasher.putUnencodedChars(type.name)
|
hasher.putUnencodedChars(type.name)
|
||||||
} else {
|
} else {
|
||||||
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
|
fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Hash the element type + some array hash
|
// Hash the element type + some array hash
|
||||||
is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
is GenericArrayType -> {
|
||||||
hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH)
|
fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
||||||
// TODO: include bounds
|
hasher, factory, debugIndent+1).putUnencodedChars(ARRAY_HASH)
|
||||||
is WildcardType -> {
|
|
||||||
hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
|
|
||||||
}
|
}
|
||||||
else -> throw NotSerializableException("Don't know how to hash")
|
else -> throw NotSerializableException("Don't know how to hash")
|
||||||
}
|
}
|
||||||
@ -443,16 +449,17 @@ private fun fingerprintForObject(
|
|||||||
alreadySeen: MutableSet<Type>,
|
alreadySeen: MutableSet<Type>,
|
||||||
hasher: Hasher,
|
hasher: Hasher,
|
||||||
factory: SerializerFactory,
|
factory: SerializerFactory,
|
||||||
offset: Int = 0): Hasher {
|
debugIndent: Int = 0): Hasher {
|
||||||
// Hash the class + properties + interfaces
|
// Hash the class + properties + interfaces
|
||||||
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
|
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
|
||||||
|
|
||||||
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
|
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
|
||||||
.serializationOrder
|
.serializationOrder
|
||||||
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
||||||
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, offset+4)
|
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, debugIndent+1)
|
||||||
.putUnencodedChars(prop.getter.name)
|
.putUnencodedChars(prop.getter.name)
|
||||||
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
||||||
}
|
}
|
||||||
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) }
|
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+4) }
|
||||||
return hasher
|
return hasher
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,12 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the properties to be used during serialization by attempting to find those that match the parameters to the
|
* Identifies the properties to be used during serialization by attempting to find those that match the parameters
|
||||||
* deserialization constructor, if the class is concrete. If it is abstract, or an interface, then use all the properties.
|
* to the deserialization constructor, if the class is concrete. If it is abstract, or an interface, then use all
|
||||||
|
* the properties.
|
||||||
*
|
*
|
||||||
* Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters have
|
* Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters
|
||||||
* names accessible via reflection.
|
* have names accessible via reflection.
|
||||||
*/
|
*/
|
||||||
internal fun <T : Any> propertiesForSerialization(
|
internal fun <T : Any> propertiesForSerialization(
|
||||||
kotlinConstructor: KFunction<T>?,
|
kotlinConstructor: KFunction<T>?,
|
||||||
@ -113,8 +114,8 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
|
|||||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||||
if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) {
|
if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) {
|
||||||
throw NotSerializableException(
|
throw NotSerializableException(
|
||||||
"Property type $returnType for $name of $clazz differs from constructor parameter "
|
"Property type '$returnType' for '$name' of '$clazz' differs from constructor parameter "
|
||||||
+ "type ${param.value.type.javaType}")
|
+ "type '${param.value.type.javaType}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair(PublicPropertyReader(getter), returnType)
|
Pair(PublicPropertyReader(getter), returnType)
|
||||||
@ -165,9 +166,20 @@ private fun propertiesForSerializationFromSetters(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean {
|
private fun constructorParamTakesReturnTypeOfGetter(
|
||||||
val typeToken = TypeToken.of(param.type.javaType)
|
getterReturnType: Type,
|
||||||
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
|
rawGetterReturnType: Type,
|
||||||
|
param: KParameter): Boolean {
|
||||||
|
val paramToken = TypeToken.of(param.type.javaType)
|
||||||
|
val rawParamType = TypeToken.of(paramToken.rawType)
|
||||||
|
|
||||||
|
return paramToken.isSupertypeOf(getterReturnType)
|
||||||
|
|| paramToken.isSupertypeOf(rawGetterReturnType)
|
||||||
|
// cope with the case where the constructor parameter is a generic type (T etc) but we
|
||||||
|
// can discover it's raw type. When bounded this wil be the bounding type, unbounded
|
||||||
|
// generics this will be object
|
||||||
|
|| rawParamType.isSupertypeOf(getterReturnType)
|
||||||
|
|| rawParamType.isSupertypeOf(rawGetterReturnType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun propertiesForSerializationFromAbstract(
|
private fun propertiesForSerializationFromAbstract(
|
||||||
|
@ -86,15 +86,15 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
|
|||||||
data.putObject(transformsSchema)
|
data.putObject(transformsSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type) {
|
internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, debugIndent: Int) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
data.putNull()
|
data.putNull()
|
||||||
} else {
|
} else {
|
||||||
writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type)
|
writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, debugIndent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun writeObject(obj: Any, data: Data, type: Type) {
|
internal fun writeObject(obj: Any, data: Data, type: Type, debugIndent: Int = 0) {
|
||||||
val serializer = serializerFactory.get(obj.javaClass, type)
|
val serializer = serializerFactory.get(obj.javaClass, type)
|
||||||
if (serializer !in serializerHistory) {
|
if (serializer !in serializerHistory) {
|
||||||
serializerHistory.add(serializer)
|
serializerHistory.add(serializer)
|
||||||
@ -103,11 +103,13 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
|
|||||||
|
|
||||||
val retrievedRefCount = objectHistory[obj]
|
val retrievedRefCount = objectHistory[obj]
|
||||||
if (retrievedRefCount == null) {
|
if (retrievedRefCount == null) {
|
||||||
serializer.writeObject(obj, data, type, this)
|
serializer.writeObject(obj, data, type, this, debugIndent)
|
||||||
// Important to do it after serialization such that dependent object will have preceding reference numbers
|
// Important to do it after serialization such that dependent object will have preceding reference numbers
|
||||||
// assigned to them first as they will be first read from the stream on receiving end.
|
// assigned to them first as they will be first read from the stream on receiving end.
|
||||||
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
|
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
|
||||||
if (suitableForObjectReference(obj.javaClass)) objectHistory.put(obj, objectHistory.size)
|
if (suitableForObjectReference(obj.javaClass)) {
|
||||||
|
objectHistory.put(obj, objectHistory.size)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data.writeReferencedObject(ReferencedObject(retrievedRefCount))
|
data.writeReferencedObject(ReferencedObject(retrievedRefCount))
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,6 @@ open class SerializerFactory(
|
|||||||
for (typeNotation in schemaAndDescriptor.schemas.schema.types) {
|
for (typeNotation in schemaAndDescriptor.schemas.schema.types) {
|
||||||
try {
|
try {
|
||||||
val serialiser = processSchemaEntry(typeNotation)
|
val serialiser = processSchemaEntry(typeNotation)
|
||||||
|
|
||||||
// if we just successfully built a serializer for the type but the type fingerprint
|
// if we just successfully built a serializer for the type but the type fingerprint
|
||||||
// doesn't match that of the serialised object then we are dealing with different
|
// doesn't match that of the serialised object then we are dealing with different
|
||||||
// instance of the class, as such we need to build an EvolutionSerializer
|
// instance of the class, as such we need to build an EvolutionSerializer
|
||||||
|
@ -22,7 +22,7 @@ class SingletonSerializer(override val type: Class<*>, val singleton: Any, facto
|
|||||||
output.writeTypeNotations(typeNotation)
|
output.writeTypeNotations(typeNotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
|
||||||
data.withDescribed(typeNotation.descriptor) {
|
data.withDescribed(typeNotation.descriptor) {
|
||||||
data.putBoolean(false)
|
data.putBoolean(false)
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,12 @@ fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type
|
|||||||
"string" -> String::class.java
|
"string" -> String::class.java
|
||||||
"binary" -> ByteArray::class.java
|
"binary" -> ByteArray::class.java
|
||||||
"*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0])
|
"*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0])
|
||||||
else -> classloader.loadClass(type)
|
else -> {
|
||||||
|
classloader.loadClass(
|
||||||
|
if (type.endsWith("?>")) {
|
||||||
|
type.substring(0, type.indexOf('<'))
|
||||||
|
} else type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AMQPField.validateType(classloader: ClassLoader) = when (type) {
|
fun AMQPField.validateType(classloader: ClassLoader) = when (type) {
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
|
import java.net.URI
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
// To regenerate any of the binary test files do the following
|
// To regenerate any of the binary test files do the following
|
||||||
|
@ -1,21 +1,39 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
|
import net.corda.testing.core.TestIdentity
|
||||||
|
import org.hibernate.Transaction
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
data class TestContractState(
|
||||||
|
override val participants: List<AbstractParty>
|
||||||
|
) : ContractState
|
||||||
|
|
||||||
|
class TestAttachmentConstraint : AttachmentConstraint {
|
||||||
|
override fun isSatisfiedBy(attachment: Attachment) = true
|
||||||
|
}
|
||||||
|
|
||||||
class GenericsTests {
|
class GenericsTests {
|
||||||
companion object {
|
companion object {
|
||||||
val VERBOSE = false
|
val VERBOSE = true
|
||||||
|
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED")
|
||||||
var localPath = projectRootDir.toUri().resolve(
|
var localPath = projectRootDir.toUri().resolve(
|
||||||
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
|
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
|
||||||
|
|
||||||
|
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit
|
private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit
|
||||||
@ -252,29 +270,235 @@ class GenericsTests {
|
|||||||
File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t)
|
File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DifferentBounds {
|
data class StateAndString(val state: TransactionState<*>, val ref: String)
|
||||||
fun go()
|
data class GenericStateAndString<out T: ContractState>(val state: TransactionState<T>, val ref: String)
|
||||||
|
|
||||||
|
//
|
||||||
|
// If this doesn't blow up all is fine
|
||||||
|
private fun fingerprintingDiffersStrip(state: Any) {
|
||||||
|
class cl : ClassLoader()
|
||||||
|
|
||||||
|
val m = ClassLoader::class.java.getDeclaredMethod("findLoadedClass", *arrayOf<Class<*>>(String::class.java))
|
||||||
|
m.isAccessible = true
|
||||||
|
|
||||||
|
val factory1 = testDefaultFactory()
|
||||||
|
factory1.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
|
||||||
|
|
||||||
|
// attempt at having a class loader without some of the derived non core types loaded and thus
|
||||||
|
// possibly altering how we serialise things
|
||||||
|
val altClassLoader = cl()
|
||||||
|
|
||||||
|
val factory2 = SerializerFactory(AllWhitelist, altClassLoader)
|
||||||
|
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
|
||||||
|
val ser2 = TestSerializationOutput(VERBOSE, factory2).serializeAndReturnSchema(state)
|
||||||
|
|
||||||
|
// now deserialise those objects
|
||||||
|
val factory3 = testDefaultFactory()
|
||||||
|
factory3.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
|
||||||
|
val des1 = DeserializationInput(factory3).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
val factory4 = SerializerFactory(AllWhitelist, cl())
|
||||||
|
factory4.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
|
||||||
|
val des2 = DeserializationInput(factory4).deserializeAndReturnEnvelope(ser2.obj)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun differentBounds() {
|
fun fingerprintingDiffers() {
|
||||||
data class A (val a: Int): DifferentBounds {
|
val state = TransactionState<TestContractState> (
|
||||||
override fun go() {
|
TestContractState(listOf(miniCorp.party)),
|
||||||
println(a)
|
"wibble", miniCorp.party,
|
||||||
}
|
encumbrance = null,
|
||||||
|
constraint = TestAttachmentConstraint())
|
||||||
|
|
||||||
|
val sas = StateAndString(state, "wibble")
|
||||||
|
|
||||||
|
fingerprintingDiffersStrip(sas)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class G<out T : DifferentBounds>(val b: T)
|
@Test
|
||||||
|
fun fingerprintingDiffersList() {
|
||||||
|
val state = TransactionState<TestContractState> (
|
||||||
|
TestContractState(listOf(miniCorp.party)),
|
||||||
|
"wibble", miniCorp.party,
|
||||||
|
encumbrance = null,
|
||||||
|
constraint = TestAttachmentConstraint())
|
||||||
|
|
||||||
val factorys = listOf(
|
val sas = StateAndString(state, "wibble")
|
||||||
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
|
||||||
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
|
|
||||||
|
|
||||||
val ser = SerializationOutput(factorys[0])
|
fingerprintingDiffersStrip(Collections.singletonList(sas))
|
||||||
|
}
|
||||||
|
|
||||||
ser.serialize(G(A(10))).apply {
|
|
||||||
factorys.forEach {
|
//
|
||||||
|
// Force object to be serialised as Example<T> and deserialized as Example<?>
|
||||||
|
//
|
||||||
|
@Test
|
||||||
|
fun fingerprintingDiffersListLoaded() {
|
||||||
|
//
|
||||||
|
// using this wrapper class we force the object to be serialised as
|
||||||
|
// net.corda.core.contracts.TransactionState<T>
|
||||||
|
//
|
||||||
|
data class TransactionStateWrapper<out T : ContractState> (val o: List<GenericStateAndString<T>>)
|
||||||
|
|
||||||
|
val state = TransactionState<TestContractState> (
|
||||||
|
TestContractState(listOf(miniCorp.party)),
|
||||||
|
"wibble", miniCorp.party,
|
||||||
|
encumbrance = null,
|
||||||
|
constraint = TestAttachmentConstraint())
|
||||||
|
|
||||||
|
val sas = GenericStateAndString(state, "wibble")
|
||||||
|
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
|
||||||
|
factory1.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
|
||||||
|
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
|
||||||
|
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(
|
||||||
|
TransactionStateWrapper(Collections.singletonList(sas)))
|
||||||
|
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(sas.ref, des1.obj.o.firstOrNull()?.ref ?: "WILL NOT MATCH")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nestedGenericsWithBound() {
|
||||||
|
open class BaseState(val a : Int)
|
||||||
|
class DState(a: Int) : BaseState(a)
|
||||||
|
data class LTransactionState<out T : BaseState> constructor(val data: T)
|
||||||
|
data class StateWrapper<out T : BaseState>(val state: LTransactionState<T>)
|
||||||
|
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
|
val state = LTransactionState(DState(1020304))
|
||||||
|
val stateAndString = StateWrapper(state)
|
||||||
|
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
|
||||||
|
|
||||||
|
//val factory2 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(state.data.a, des1.obj.state.data.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nestedMultiGenericsWithBound() {
|
||||||
|
open class BaseState(val a : Int)
|
||||||
|
class DState(a: Int) : BaseState(a)
|
||||||
|
class EState(a: Int, val msg: String) : BaseState(a)
|
||||||
|
|
||||||
|
data class LTransactionState<out T1 : BaseState, out T2: BaseState> (val data: T1, val context: T2)
|
||||||
|
data class StateWrapper<out T1 : BaseState, out T2: BaseState>(val state: LTransactionState<T1, T2>)
|
||||||
|
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
|
val state = LTransactionState(DState(1020304), EState(5060708, msg = "thigns"))
|
||||||
|
val stateAndString = StateWrapper(state)
|
||||||
|
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
|
||||||
|
|
||||||
|
//val factory2 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(state.data.a, des1.obj.state.data.a)
|
||||||
|
assertEquals(state.context.a, des1.obj.state.context.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nestedMultiGenericsNoBound() {
|
||||||
|
open class BaseState(val a : Int)
|
||||||
|
class DState(a: Int) : BaseState(a)
|
||||||
|
class EState(a: Int, val msg: String) : BaseState(a)
|
||||||
|
|
||||||
|
data class LTransactionState<out T1, out T2> (val data: T1, val context: T2)
|
||||||
|
data class StateWrapper<out T1, out T2>(val state: LTransactionState<T1, T2>)
|
||||||
|
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
|
val state = LTransactionState(DState(1020304), EState(5060708, msg = "things"))
|
||||||
|
val stateAndString = StateWrapper(state)
|
||||||
|
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
|
||||||
|
|
||||||
|
//val factory2 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(state.data.a, des1.obj.state.data.a)
|
||||||
|
assertEquals(state.context.a, des1.obj.state.context.a)
|
||||||
|
assertEquals(state.context.msg, des1.obj.state.context.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun baseClassInheritedButNotOverriden() {
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
|
||||||
|
open class BaseState<T1, T2>(open val a : T1, open val b: T2)
|
||||||
|
class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
|
||||||
|
|
||||||
|
val state = DState(100, "hello")
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(state.a, des1.obj.a)
|
||||||
|
assertEquals(state.b, des1.obj.b)
|
||||||
|
|
||||||
|
class DState2<T1, T2, T3>(a: T1, b: T2, val c: T3) : BaseState<T1, T2>(a, b)
|
||||||
|
|
||||||
|
val state2 = DState2(100, "hello", 100L)
|
||||||
|
val ser2 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state2)
|
||||||
|
val des2 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser2.obj)
|
||||||
|
|
||||||
|
assertEquals(state2.a, des2.obj.a)
|
||||||
|
assertEquals(state2.b, des2.obj.b)
|
||||||
|
assertEquals(state2.c, des2.obj.c)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun baseClassInheritedButNotOverridenBounded() {
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
|
||||||
|
open class Bound(val a: Int)
|
||||||
|
|
||||||
|
open class BaseState<out T1 : Bound>(open val a: T1)
|
||||||
|
class DState<out T1: Bound>(a: T1) : BaseState<T1>(a)
|
||||||
|
|
||||||
|
val state = DState(Bound(100))
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(state.a.a, des1.obj.a.a)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nestedMultiGenericsAtBottomWithBound() {
|
||||||
|
open class BaseState<T1, T2>(val a : T1, val b: T2)
|
||||||
|
class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
|
||||||
|
class EState<T1, T2>(a: T1, b: T2, val c: Long) : BaseState<T1, T2>(a, b)
|
||||||
|
|
||||||
|
data class LTransactionState<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>> (val data: T3, val context: T4)
|
||||||
|
data class StateWrapper<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>>(val state: LTransactionState<T1, T2, T3, T4>)
|
||||||
|
|
||||||
|
val factory1 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
|
val state = LTransactionState(DState(1020304, "Hello"), EState(5060708, "thins", 100L))
|
||||||
|
val stateAndString = StateWrapper(state)
|
||||||
|
|
||||||
|
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
|
||||||
|
|
||||||
|
//val factory2 = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactory()
|
||||||
|
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
|
||||||
|
|
||||||
|
assertEquals(state.data.a, des1.obj.state.data.a)
|
||||||
|
assertEquals(state.context.a, des1.obj.state.context.a)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user