mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
CORDA-992 - Make the finger printer pluggable for serialization factory (#2479)
Facilitates easier testing
This commit is contained in:
parent
d072f6c275
commit
3c4212a3d6
@ -6,6 +6,8 @@ from the previous milestone release.
|
||||
|
||||
UNRELEASED
|
||||
----------
|
||||
* Make the serialisation finger-printer a pluggable entity rather than hard wiring into the factory
|
||||
|
||||
* Removed blacklisted word checks in Corda X.500 name to allow "Server" or "Node" to be use as part of the legal name.
|
||||
|
||||
* Separated our pre-existing Artemis broker into an RPC broker and a P2P broker.
|
||||
|
@ -31,7 +31,8 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
||||
"${type.componentType().typeName}$arrayType"
|
||||
}
|
||||
|
||||
override val typeDescriptor by lazy { Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}") }
|
||||
override val typeDescriptor by lazy {
|
||||
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") }
|
||||
internal val elementType: Type by lazy { type.componentType() }
|
||||
internal open val typeName by lazy { calcTypeName(type) }
|
||||
|
||||
|
@ -17,7 +17,9 @@ import kotlin.collections.Set
|
||||
*/
|
||||
class CollectionSerializer(val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||
override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType))
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
||||
override val typeDescriptor by lazy {
|
||||
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
// NB: Order matters in this map, the most specific classes should be listed at the end
|
||||
|
@ -60,7 +60,9 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
|
||||
|
||||
override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == this.clazz
|
||||
override val type: Type get() = clazz
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForDescriptors(superClassSerializer.typeDescriptor.toString(), nameForType(clazz))}")
|
||||
override val typeDescriptor by lazy {
|
||||
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${SerializerFingerPrinter().fingerprintForDescriptors(superClassSerializer.typeDescriptor.toString(), nameForType(clazz))}")
|
||||
}
|
||||
private val typeNotation: TypeNotation = RestrictedType(
|
||||
SerializerFactory.nameForType(clazz),
|
||||
null,
|
||||
|
@ -39,7 +39,8 @@ class EnumEvolutionSerializer(
|
||||
factory: SerializerFactory,
|
||||
private val conversions: Map<String, String>,
|
||||
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")!!
|
||||
override val typeDescriptor = Symbol.valueOf(
|
||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")!!
|
||||
|
||||
companion object {
|
||||
private fun MutableMap<String, String>.mapInPlace(f: (String) -> String) {
|
||||
|
@ -11,8 +11,9 @@ import java.lang.reflect.Type
|
||||
*/
|
||||
class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||
override val type: Type = declaredType
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")!!
|
||||
private val typeNotation: TypeNotation
|
||||
override val typeDescriptor = Symbol.valueOf(
|
||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")!!
|
||||
|
||||
init {
|
||||
typeNotation = RestrictedType(
|
||||
|
@ -0,0 +1,202 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import com.google.common.hash.Hasher
|
||||
import com.google.common.hash.Hashing
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.toBase64
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Should be implemented by classes which wish to provide plugable fingerprinting og types for a [SerializerFactory]
|
||||
*/
|
||||
interface FingerPrinter {
|
||||
/**
|
||||
* Return a unique identifier for a type, usually this will take into account the constituent elements
|
||||
* of said type such that any modification to any sub element wll generate a different fingerprint
|
||||
*/
|
||||
fun fingerprint(type: Type): String
|
||||
|
||||
/**
|
||||
* If required, associate an instance of the fingerprinter with a specific serializer factory
|
||||
*/
|
||||
fun setOwner(factory: SerializerFactory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the finger printing mechanism used by default
|
||||
*/
|
||||
class SerializerFingerPrinter : FingerPrinter {
|
||||
private var factory: SerializerFactory? = null
|
||||
|
||||
private val ARRAY_HASH: String = "Array = true"
|
||||
private val ENUM_HASH: String = "Enum = true"
|
||||
private val ALREADY_SEEN_HASH: String = "Already seen = true"
|
||||
private val NULLABLE_HASH: String = "Nullable = true"
|
||||
private val NOT_NULLABLE_HASH: String = "Nullable = false"
|
||||
private val ANY_TYPE_HASH: String = "Any type = true"
|
||||
private val TYPE_VARIABLE_HASH: String = "Type variable = true"
|
||||
private val WILDCARD_TYPE_HASH: String = "Wild card = true"
|
||||
|
||||
private val logger by lazy { loggerFor<Schema>() }
|
||||
|
||||
override fun setOwner(factory: SerializerFactory) {
|
||||
this.factory = factory
|
||||
}
|
||||
|
||||
/**
|
||||
* The method generates a fingerprint for a given JVM [Type] that should be unique to the schema representation.
|
||||
* Thus it only takes into account properties and types and only supports the same object graph subset as the overall
|
||||
* serialization code.
|
||||
*
|
||||
* The idea being that even for two classes that share the same name but differ in a minor way, the fingerprint will be
|
||||
* different.
|
||||
*/
|
||||
override fun fingerprint(type: Type): String {
|
||||
return fingerprintForType(
|
||||
type, null, HashSet(), Hashing.murmur3_128().newHasher(), debugIndent = 1).hash().asBytes().toBase64()
|
||||
}
|
||||
|
||||
private fun isCollectionOrMap(type: Class<*>) =
|
||||
(Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type))
|
||||
&& !EnumSet::class.java.isAssignableFrom(type)
|
||||
|
||||
internal fun fingerprintForDescriptors(vararg typeDescriptors: String): String {
|
||||
val hasher = Hashing.murmur3_128().newHasher()
|
||||
for (typeDescriptor in typeDescriptors) {
|
||||
hasher.putUnencodedChars(typeDescriptor)
|
||||
}
|
||||
return hasher.hash().asBytes().toBase64()
|
||||
}
|
||||
|
||||
private fun Hasher.fingerprintWithCustomSerializerOrElse(
|
||||
factory: SerializerFactory,
|
||||
clazz: Class<*>,
|
||||
declaredType: Type,
|
||||
block: () -> Hasher): Hasher {
|
||||
// Need to check if a custom serializer is applicable
|
||||
val customSerializer = factory.findCustomSerializer(clazz, declaredType)
|
||||
return if (customSerializer != null) {
|
||||
putUnencodedChars(customSerializer.typeDescriptor)
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
|
||||
hasher: Hasher, debugIndent: Int = 1): Hasher {
|
||||
// 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
|
||||
// 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
|
||||
// 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<*>)
|
||||
&& (type !is WildcardType)) {
|
||||
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
||||
} else {
|
||||
alreadySeen += type
|
||||
try {
|
||||
when (type) {
|
||||
is ParameterizedType -> {
|
||||
// Hash the rawType + params
|
||||
val clazz = type.rawType as Class<*>
|
||||
|
||||
val startingHash = if (isCollectionOrMap(clazz)) {
|
||||
hasher.putUnencodedChars(clazz.name)
|
||||
} else {
|
||||
hasher.fingerprintWithCustomSerializerOrElse(factory!!, clazz, type) {
|
||||
fingerprintForObject(type, type, alreadySeen, hasher, factory!!, debugIndent + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// ... and concatenate the type data for each parameter type.
|
||||
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
||||
fingerprintForType(paramType, type, alreadySeen, orig, debugIndent + 1)
|
||||
}
|
||||
}
|
||||
// Previously, we drew a distinction between TypeVariable, WildcardType, and AnyType, changing
|
||||
// the signature of the fingerprinted object. This, however, doesn't work as it breaks bi-
|
||||
// directional fingerprints. That is, fingerprinting a concrete instance of a generic
|
||||
// type (Example<Int>), creates a different fingerprint from the generic type itself (Example<T>)
|
||||
//
|
||||
// On serialization Example<Int> is treated as Example<T>, a TypeVariable
|
||||
// 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 WildcardType,
|
||||
is TypeVariable<*> -> {
|
||||
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
|
||||
}
|
||||
is Class<*> -> {
|
||||
if (type.isArray) {
|
||||
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, debugIndent + 1)
|
||||
.putUnencodedChars(ARRAY_HASH)
|
||||
} else if (SerializerFactory.isPrimitive(type)) {
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else if (isCollectionOrMap(type)) {
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else if (type.isEnum) {
|
||||
// ensures any change to the enum (adding constants) will trigger the need for evolution
|
||||
hasher.apply {
|
||||
type.enumConstants.forEach {
|
||||
putUnencodedChars(it.toString())
|
||||
}
|
||||
}.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH)
|
||||
} else {
|
||||
hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) {
|
||||
if (type.kotlin.objectInstance != null) {
|
||||
// TODO: name collision is too likely for kotlin objects, we need to introduce some reference
|
||||
// to the CorDapp but maybe reference to the JAR in the short term.
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else {
|
||||
fingerprintForObject(type, type, alreadySeen, hasher, factory!!, debugIndent + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hash the element type + some array hash
|
||||
is GenericArrayType -> {
|
||||
fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
||||
hasher, debugIndent + 1).putUnencodedChars(ARRAY_HASH)
|
||||
}
|
||||
else -> throw NotSerializableException("Don't know how to hash")
|
||||
}
|
||||
} catch (e: NotSerializableException) {
|
||||
val msg = "${e.message} -> $type"
|
||||
logger.error(msg, e)
|
||||
throw NotSerializableException(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fingerprintForObject(
|
||||
type: Type,
|
||||
contextType: Type?,
|
||||
alreadySeen: MutableSet<Type>,
|
||||
hasher: Hasher,
|
||||
factory: SerializerFactory,
|
||||
debugIndent: Int = 0): Hasher {
|
||||
// Hash the class + properties + interfaces
|
||||
val name = type.asClass()?.name
|
||||
?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
|
||||
|
||||
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
|
||||
.serializationOrder
|
||||
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
||||
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, debugIndent + 1)
|
||||
.putUnencodedChars(prop.getter.name)
|
||||
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
||||
}
|
||||
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, debugIndent + 1) }
|
||||
return hasher
|
||||
}
|
||||
}
|
@ -19,7 +19,8 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *>
|
||||
*/
|
||||
class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||
override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType))
|
||||
override val typeDescriptor: Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
||||
override val typeDescriptor: Symbol = Symbol.valueOf(
|
||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||
|
||||
companion object {
|
||||
// NB: Order matters in this map, the most specific classes should be listed at the end
|
||||
|
@ -31,7 +31,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
|
||||
private val typeName = nameForType(clazz)
|
||||
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
||||
override val typeDescriptor = Symbol.valueOf(
|
||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||
|
||||
// We restrict to only those annotated or whitelisted
|
||||
private val interfaces = interfacesForSerialization(clazz, factory)
|
||||
|
@ -1,19 +1,13 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import com.google.common.hash.Hasher
|
||||
import com.google.common.hash.Hashing
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.toBase64
|
||||
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.DescribedTypeConstructor
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.*
|
||||
import java.util.*
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
|
||||
|
||||
@ -92,7 +86,14 @@ 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 {
|
||||
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 = AMQPDescriptorRegistry.FIELD.amqpDescriptor
|
||||
|
||||
@ -302,162 +303,4 @@ data class ReferencedObject(private val refCounter: Int) : DescribedType {
|
||||
override fun toString(): String = "<refObject refCounter=$refCounter/>"
|
||||
}
|
||||
|
||||
private val ARRAY_HASH: String = "Array = true"
|
||||
private val ENUM_HASH: String = "Enum = true"
|
||||
private val ALREADY_SEEN_HASH: String = "Already seen = true"
|
||||
private val NULLABLE_HASH: String = "Nullable = true"
|
||||
private val NOT_NULLABLE_HASH: String = "Nullable = false"
|
||||
private val ANY_TYPE_HASH: String = "Any type = true"
|
||||
private val TYPE_VARIABLE_HASH: String = "Type variable = true"
|
||||
private val WILDCARD_TYPE_HASH: String = "Wild card = true"
|
||||
|
||||
private val logger by lazy { loggerFor<Schema>() }
|
||||
|
||||
/**
|
||||
* The method generates a fingerprint for a given JVM [Type] that should be unique to the schema representation.
|
||||
* Thus it only takes into account properties and types and only supports the same object graph subset as the overall
|
||||
* serialization code.
|
||||
*
|
||||
* The idea being that even for two classes that share the same name but differ in a minor way, the fingerprint will be
|
||||
* different.
|
||||
*/
|
||||
// TODO: write tests
|
||||
internal fun fingerprintForType(type: Type, factory: SerializerFactory): String {
|
||||
return fingerprintForType(type, null, HashSet(), Hashing.murmur3_128().newHasher(), factory).hash().asBytes().toBase64()
|
||||
}
|
||||
|
||||
internal fun fingerprintForDescriptors(vararg typeDescriptors: String): String {
|
||||
val hasher = Hashing.murmur3_128().newHasher()
|
||||
for (typeDescriptor in typeDescriptors) {
|
||||
hasher.putUnencodedChars(typeDescriptor)
|
||||
}
|
||||
return hasher.hash().asBytes().toBase64()
|
||||
}
|
||||
|
||||
private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFactory, clazz: Class<*>, declaredType: Type, block: () -> Hasher): Hasher {
|
||||
// Need to check if a custom serializer is applicable
|
||||
val customSerializer = factory.findCustomSerializer(clazz, declaredType)
|
||||
return if (customSerializer != null) {
|
||||
putUnencodedChars(customSerializer.typeDescriptor)
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
return if ((type in alreadySeen)
|
||||
&& (type !is SerializerFactory.AnyType)
|
||||
&& (type !is TypeVariable<*>)
|
||||
&& (type !is WildcardType)) {
|
||||
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
||||
} else {
|
||||
alreadySeen += type
|
||||
try {
|
||||
when (type) {
|
||||
is ParameterizedType -> {
|
||||
// Hash the rawType + params
|
||||
val clazz = type.rawType as Class<*>
|
||||
|
||||
val startingHash = if (isCollectionOrMap(clazz)) {
|
||||
hasher.putUnencodedChars(clazz.name)
|
||||
} else {
|
||||
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
|
||||
fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1)
|
||||
}
|
||||
}
|
||||
|
||||
// ... and concatenate the type data for each parameter type.
|
||||
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
||||
fingerprintForType(paramType, type, alreadySeen, orig, factory, debugIndent+1)
|
||||
}
|
||||
}
|
||||
// Previously, we drew a distinction between TypeVariable, WildcardType, and AnyType, changing
|
||||
// the signature of the fingerprinted object. This, however, doesn't work as it breaks bi-
|
||||
// directional fingerprints. That is, fingerprinting a concrete instance of a generic
|
||||
// type (Example<Int>), creates a different fingerprint from the generic type itself (Example<T>)
|
||||
//
|
||||
// On serialization Example<Int> is treated as Example<T>, a TypeVariable
|
||||
// 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 WildcardType,
|
||||
is TypeVariable<*> -> {
|
||||
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
|
||||
}
|
||||
is Class<*> -> {
|
||||
if (type.isArray) {
|
||||
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, debugIndent+1)
|
||||
.putUnencodedChars(ARRAY_HASH)
|
||||
} else if (SerializerFactory.isPrimitive(type)) {
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else if (isCollectionOrMap(type)) {
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else if (type.isEnum) {
|
||||
// ensures any change to the enum (adding constants) will trigger the need for evolution
|
||||
hasher.apply {
|
||||
type.enumConstants.forEach {
|
||||
putUnencodedChars(it.toString())
|
||||
}
|
||||
}.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH)
|
||||
} else {
|
||||
hasher.fingerprintWithCustomSerializerOrElse(factory, type, type) {
|
||||
if (type.kotlin.objectInstance != null) {
|
||||
// TODO: name collision is too likely for kotlin objects, we need to introduce some reference
|
||||
// to the CorDapp but maybe reference to the JAR in the short term.
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else {
|
||||
fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hash the element type + some array hash
|
||||
is GenericArrayType -> {
|
||||
fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
||||
hasher, factory, debugIndent+1).putUnencodedChars(ARRAY_HASH)
|
||||
}
|
||||
else -> throw NotSerializableException("Don't know how to hash")
|
||||
}
|
||||
} catch (e: NotSerializableException) {
|
||||
val msg = "${e.message} -> $type"
|
||||
logger.error(msg, e)
|
||||
throw NotSerializableException(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) &&
|
||||
!EnumSet::class.java.isAssignableFrom(type)
|
||||
|
||||
private fun fingerprintForObject(
|
||||
type: Type,
|
||||
contextType: Type?,
|
||||
alreadySeen: MutableSet<Type>,
|
||||
hasher: Hasher,
|
||||
factory: SerializerFactory,
|
||||
debugIndent: Int = 0): Hasher {
|
||||
// Hash the class + properties + interfaces
|
||||
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
|
||||
|
||||
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
|
||||
.serializationOrder
|
||||
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
||||
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, debugIndent+1)
|
||||
.putUnencodedChars(prop.getter.name)
|
||||
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
||||
}
|
||||
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+1) }
|
||||
return hasher
|
||||
}
|
||||
|
@ -40,7 +40,13 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
|
||||
open class SerializerFactory(
|
||||
val whitelist: ClassWhitelist,
|
||||
cl: ClassLoader,
|
||||
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter()) {
|
||||
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
val fingerPrinter: FingerPrinter = SerializerFingerPrinter()) {
|
||||
|
||||
init {
|
||||
fingerPrinter.setOwner(this)
|
||||
}
|
||||
|
||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
|
||||
|
@ -10,7 +10,8 @@ import java.lang.reflect.Type
|
||||
* want converting back to that singleton instance on the receiving JVM.
|
||||
*/
|
||||
class SingletonSerializer(override val type: Class<*>, val singleton: Any, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
||||
override val typeDescriptor = Symbol.valueOf(
|
||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||
|
||||
private val interfaces = interfacesForSerialization(type, factory)
|
||||
|
||||
|
@ -32,10 +32,12 @@ public class ErrorMessageTests {
|
||||
@Test
|
||||
public void testJavaConstructorAnnotations() {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
|
||||
|
@ -29,7 +29,8 @@ public class JavaGenericsTest {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter());
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
SerializedBytes<?> bytes = ser.serialize(a1);
|
||||
@ -44,7 +45,8 @@ public class JavaGenericsTest {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter());
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
return (new SerializationOutput(factory)).serialize(a);
|
||||
}
|
||||
@ -59,7 +61,8 @@ public class JavaGenericsTest {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter());
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
return des.deserialize(bytes, A.class);
|
||||
@ -83,7 +86,8 @@ public class JavaGenericsTest {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter());
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory);
|
||||
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();
|
||||
|
@ -76,9 +76,9 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
@Test
|
||||
public void singlePrivateBooleanWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter);
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -89,9 +89,10 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
@Test
|
||||
public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter);
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -103,9 +104,9 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
@Test
|
||||
public void testCapitilsationOfIs() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter);
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -119,9 +120,9 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
@Test
|
||||
public void singlePrivateIntWithBoolean() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter);
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -134,9 +135,10 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
@Test
|
||||
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter);
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -164,9 +166,11 @@ public class JavaPrivatePropertyTests {
|
||||
@Test
|
||||
public void singlePrivateWithConstructorAndGetter()
|
||||
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(), evolutionSerializerGetter);
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
|
@ -29,9 +29,9 @@ public class JavaSerialiseEnumTests {
|
||||
public void testJavaConstructorAnnotations() throws NotSerializableException {
|
||||
Bra bra = new Bra(Bras.UNDERWIRE);
|
||||
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
SerializedBytes<Object> bytes = ser.serialize(bra);
|
||||
}
|
||||
|
@ -173,10 +173,13 @@ public class JavaSerializationOutputTests {
|
||||
|
||||
private Object serdes(Object obj) throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
SerializedBytes<Object> bytes = ser.serialize(obj);
|
||||
|
||||
|
@ -125,9 +125,12 @@ public class ListsSerializationJavaTest {
|
||||
|
||||
// Have to have own version as Kotlin inline functions cannot be easily called from Java
|
||||
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter,
|
||||
fingerPrinter);
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
SerializedBytes<Object> bytes = ser.serialize(container);
|
||||
DeserializationInput des = new DeserializationInput(factory1);
|
||||
|
@ -110,10 +110,12 @@ public class SetterConstructorTests {
|
||||
@Test
|
||||
public void serialiseC() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
|
||||
@ -184,10 +186,12 @@ public class SetterConstructorTests {
|
||||
@Test
|
||||
public void deserialiseC() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
|
||||
C cPre1 = new C();
|
||||
|
||||
@ -251,10 +255,12 @@ public class SetterConstructorTests {
|
||||
@Test
|
||||
public void serialiseOuterAndInner() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
|
||||
Inner1 i1 = new Inner1("Hello");
|
||||
Inner2 i2 = new Inner2();
|
||||
@ -277,10 +283,12 @@ public class SetterConstructorTests {
|
||||
@Test
|
||||
public void typeMistmatch() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
|
||||
TypeMismatch tm = new TypeMismatch();
|
||||
tm.setA(10);
|
||||
@ -293,10 +301,12 @@ public class SetterConstructorTests {
|
||||
@Test
|
||||
public void typeMistmatch2() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter);
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
|
||||
TypeMismatch2 tm = new TypeMismatch2();
|
||||
tm.setA("10");
|
||||
|
@ -0,0 +1,55 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.test.assertEquals
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
|
||||
class FingerPrinterTesting : FingerPrinter {
|
||||
private var index = 0
|
||||
private val cache = mutableMapOf<Type, String>()
|
||||
|
||||
override fun fingerprint(type: Type): String {
|
||||
return cache.computeIfAbsent(type) { index++.toString() }
|
||||
}
|
||||
|
||||
override fun setOwner(factory: SerializerFactory) {
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
fun changeFingerprint(type: Type) {
|
||||
cache.computeIfAbsent(type) { "" }.apply { index++.toString() }
|
||||
}
|
||||
}
|
||||
|
||||
class FingerPrinterTestingTests {
|
||||
companion object {
|
||||
val VERBOSE = true
|
||||
}
|
||||
@Test
|
||||
fun testingTest() {
|
||||
val fpt = FingerPrinterTesting()
|
||||
assertEquals ("0", fpt.fingerprint(Integer::class.java))
|
||||
assertEquals ("1", fpt.fingerprint(String::class.java))
|
||||
assertEquals ("0", fpt.fingerprint(Integer::class.java))
|
||||
assertEquals ("1", fpt.fingerprint(String::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun worksAsReplacement() {
|
||||
data class C (val a: Int, val b: Long)
|
||||
|
||||
val factory = SerializerFactory(
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
EvolutionSerializerGetterTesting(),
|
||||
FingerPrinterTesting())
|
||||
|
||||
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))
|
||||
|
||||
assertEquals (1, blob.schema.types.size)
|
||||
assertEquals ("<descriptor name=\"net.corda:0\"/>", blob.schema.types[0].descriptor.toString())
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user