mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
Corda 1922 serialize states with calculated values (#3938)
* Introduce SerializeForCarpenter annotation * Apply SerializableComputedProperty annotation to Cash.exitKeys, fix bugs * info -> trace * Remove annotation from FungibleAsset, as we do not know whether all implementing classes will provide the property as a calculated value * Remove redundant import * Explicit lambda params * Restore explicit import for Enum valueOf * Moving and rescoping * More meaningful error message * Add java test and documentation * Fix accidentally broken unit test * Ignore superclass annotation if property not calculated in implementing class * Exclude calculated properties from Jackson serialisation * Fix broken test
This commit is contained in:
parent
9c8a1cd14a
commit
b6f2532ce6
@ -98,7 +98,7 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier()
|
||||
val ctor = constructorForDeserialization(beanClass)
|
||||
val amqpProperties = propertiesForSerialization(ctor, beanClass, serializerFactory)
|
||||
.serializationOrder
|
||||
.map { it.serializer.name }
|
||||
.mapNotNull { if (it.isCalculated) null else it.serializer.name }
|
||||
val propertyRenames = beanDesc.findProperties().associateBy({ it.name }, { it.internalName })
|
||||
(amqpProperties - propertyRenames.values).let {
|
||||
check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" }
|
||||
|
@ -3,6 +3,7 @@ package net.corda.core.contracts
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
@ -38,6 +39,7 @@ interface FungibleAsset<T : Any> : OwnableState {
|
||||
* There must be an ExitCommand signed by these keys to destroy the amount. While all states require their
|
||||
* owner to sign, some (i.e. cash) also require the issuer.
|
||||
*/
|
||||
@get:SerializableCalculatedProperty
|
||||
val exitKeys: Collection<PublicKey>
|
||||
|
||||
/**
|
||||
|
@ -19,4 +19,12 @@ import java.lang.annotation.Inherited
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Inherited
|
||||
annotation class CordaSerializable
|
||||
annotation class CordaSerializable
|
||||
|
||||
|
||||
/**
|
||||
* Used to annotate methods which expose calculated values that we want to be serialized for use by the class carpenter.
|
||||
*/
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION)
|
||||
annotation class SerializableCalculatedProperty
|
@ -556,10 +556,33 @@ be able to use reflection over the deserialized data, for scripting languages th
|
||||
ensuring classes not on the classpath can be deserialized without loading potentially malicious code.
|
||||
|
||||
If the original class implements some interfaces then the carpenter will make sure that all of the interface methods are
|
||||
backed by feilds. If that's not the case then an exception will be thrown during deserialization. This check can
|
||||
backed by fields. If that's not the case then an exception will be thrown during deserialization. This check can
|
||||
be turned off with ``SerializationContext.withLenientCarpenter``. This can be useful if only the field getters are needed,
|
||||
say in an object viewer.
|
||||
|
||||
Calculated values
|
||||
`````````````````
|
||||
|
||||
In some cases, for example the `exitKeys` field in ``FungibleState``, a property in an interface may normally be implemented
|
||||
as a *calculated* value, with a "getter" method for reading it but neither a corresponding constructor parameter nor a
|
||||
"setter" method for writing it. In this case, it will not automatically be included among the properties to be serialized,
|
||||
since the receiving class would ordinarily be able to re-calculate it on demand. However, a synthesized class will not
|
||||
have the method implementation which knows how to calculate the value, and a cast to the interface will fail because the
|
||||
property is not serialized and so the "getter" method present in the interface will not be synthesized.
|
||||
|
||||
The solution is to annotate the method with the ``SerializableCalculatedProperty`` annotation, which will cause the value
|
||||
exposed by the method to be read and transmitted during serialization, but discarded during normal deserialization. The
|
||||
synthesized class will then include a backing field together with a "getter" for the serialized calculated value, and will
|
||||
remain compatible with the interface.
|
||||
|
||||
If the annotation is added to the method in the *interface*, then all implementing classes must calculate the value and
|
||||
none may have a corresponding backing field; alternatively, it can be added to the overriding method on each implementing
|
||||
class where the value is calculated and there is no backing field. If the field is a Kotlin ``val``, then the annotation
|
||||
should be targeted at its getter method, e.g. ``@get:SerializableCalculatedProperty``.
|
||||
|
||||
Future enhancements
|
||||
```````````````````
|
||||
|
||||
Possible future enhancements include:
|
||||
|
||||
#. Java singleton support. We will add support for identifying classes which are singletons and identifying the
|
||||
|
@ -16,6 +16,7 @@ import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.schemas.QueryableState
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection
|
||||
|
@ -34,6 +34,7 @@ import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
@ -127,7 +128,7 @@ class VaultSoftLockManagerTest {
|
||||
override val owner get() = participants[0]
|
||||
override fun withNewOwner(newOwner: AbstractParty) = throw UnsupportedOperationException()
|
||||
override val amount get() = Amount(1, Issued(PartyAndReference(owner, OpaqueBytes.of(1)), Unit))
|
||||
override val exitKeys get() = throw UnsupportedOperationException()
|
||||
override val exitKeys get() = emptyList<PublicKey>()
|
||||
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Unit>>, newOwner: AbstractParty) = throw UnsupportedOperationException()
|
||||
override fun equals(other: Any?) = other is FungibleAssetImpl && participants == other.participants
|
||||
override fun hashCode() = participants.hashCode()
|
||||
|
@ -117,7 +117,7 @@ class DeserializationInput constructor(
|
||||
des {
|
||||
val envelope = getEnvelope(bytes, context.encodingWhitelist)
|
||||
|
||||
logger.trace("deserialize blob scheme=\"${envelope.schema.toString()}\"")
|
||||
logger.trace("deserialize blob scheme=\"${envelope.schema}\"")
|
||||
|
||||
clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema),
|
||||
clazz, context))
|
||||
@ -149,7 +149,7 @@ class DeserializationInput constructor(
|
||||
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
|
||||
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
|
||||
val objectIndex = (obj.described as UnsignedInteger).toInt()
|
||||
if (objectIndex !in 0..objectHistory.size)
|
||||
if (objectIndex >= objectHistory.size)
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Retrieval of existing reference failed. Requested index $objectIndex " +
|
||||
|
@ -125,7 +125,7 @@ abstract class EvolutionSerializer(
|
||||
// any particular NonNullable annotation type to indicate cross
|
||||
// compiler nullability
|
||||
val isKotlin = (new.type.javaClass.declaredAnnotations.any {
|
||||
it.annotationClass.qualifiedName == "kotlin.Metadata"
|
||||
it.annotationClass.qualifiedName == "kotlin.Metadata"
|
||||
})
|
||||
|
||||
constructor.parameters.withIndex().forEach {
|
||||
@ -270,8 +270,8 @@ class EvolutionSerializerViaSetters(
|
||||
* be an object that returns an [EvolutionSerializer]. Of course, any implementation that
|
||||
* extends this class can be written to invoke whatever behaviour is desired.
|
||||
*/
|
||||
abstract class EvolutionSerializerGetterBase {
|
||||
abstract fun getEvolutionSerializer(
|
||||
interface EvolutionSerializerProvider {
|
||||
fun getEvolutionSerializer(
|
||||
factory: SerializerFactory,
|
||||
typeNotation: TypeNotation,
|
||||
newSerializer: AMQPSerializer<Any>,
|
||||
@ -283,12 +283,12 @@ abstract class EvolutionSerializerGetterBase {
|
||||
* between the received schema and the class as it exists now on the class path,
|
||||
*/
|
||||
@KeepForDJVM
|
||||
class EvolutionSerializerGetter : EvolutionSerializerGetterBase() {
|
||||
object DefaultEvolutionSerializerProvider : EvolutionSerializerProvider {
|
||||
override fun getEvolutionSerializer(factory: SerializerFactory,
|
||||
typeNotation: TypeNotation,
|
||||
newSerializer: AMQPSerializer<Any>,
|
||||
schemas: SerializationSchemas): AMQPSerializer<Any> {
|
||||
return factory.serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
||||
return factory.registerByDescriptor(typeNotation.descriptor.name!!) {
|
||||
when (typeNotation) {
|
||||
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory)
|
||||
is RestrictedType -> {
|
||||
@ -310,5 +310,4 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.internal.isConcreteClass
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.nameForType
|
||||
@ -61,7 +62,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
context: SerializationContext,
|
||||
debugIndent: Int) = ifThrowsAppend({ clazz.typeName }
|
||||
) {
|
||||
if (propertySerializers.size != javaConstructor?.parameterCount &&
|
||||
if (propertySerializers.deserializableSize != javaConstructor?.parameterCount &&
|
||||
javaConstructor?.parameterCount ?: 0 > 0
|
||||
) {
|
||||
throw AMQPNotSerializableException(type, "Serialization constructor for class $type expects "
|
||||
@ -86,8 +87,9 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
input: DeserializationInput,
|
||||
context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) {
|
||||
if (obj is List<*>) {
|
||||
if (obj.size > propertySerializers.size) {
|
||||
throw AMQPNotSerializableException(type, "Too many properties in described type $typeName")
|
||||
if (obj.size != propertySerializers.size) {
|
||||
throw AMQPNotSerializableException(type, "${obj.size} objects to deserialize, but " +
|
||||
"${propertySerializers.size} properties in described type $typeName")
|
||||
}
|
||||
|
||||
return if (propertySerializers.byConstructor) {
|
||||
@ -109,8 +111,19 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
|
||||
return construct(propertySerializers.serializationOrder
|
||||
.zip(obj)
|
||||
.map { Pair(it.first.initialPosition, it.first.serializer.readProperty(it.second, schemas, input, context)) }
|
||||
.sortedWith(compareBy({ it.first }))
|
||||
.mapNotNull { (accessor, obj) ->
|
||||
// Ensure values get read out of input no matter what
|
||||
val value = accessor.serializer.readProperty(obj, schemas, input, context)
|
||||
|
||||
when(accessor) {
|
||||
is PropertyAccessorConstructor -> accessor.initialPosition to value
|
||||
is CalculatedPropertyAccessor -> null
|
||||
else -> throw UnsupportedOperationException(
|
||||
"${accessor::class.simpleName} accessor not supported " +
|
||||
"for constructor-based object building")
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy { it.first })
|
||||
.map { it.second })
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package net.corda.serialization.internal.amqp
|
||||
import com.google.common.reflect.TypeToken
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.internal.isPublic
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.serialization.internal.amqp.MethodClassifier.*
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
@ -84,7 +85,7 @@ private val propertyMethodRegex = Regex("(?<type>get|set|is)(?<var>\\p{Lu}.*)")
|
||||
* take a single parameter of a type compatible with exampleProperty and isExampleProperty must
|
||||
* return a boolean
|
||||
*/
|
||||
fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
|
||||
internal fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
|
||||
val fieldProperties = superclassChain().declaredFields().byFieldName()
|
||||
|
||||
return superclassChain().declaredMethods()
|
||||
@ -96,9 +97,23 @@ fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
|
||||
.validated()
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain [PropertyDescriptor]s for those calculated properties of a class which are annotated with
|
||||
* [SerializableCalculatedProperty]
|
||||
*/
|
||||
internal fun Class<out Any?>.calculatedPropertyDescriptors(): Map<String, PropertyDescriptor> =
|
||||
superclassChain().withInterfaces().declaredMethods()
|
||||
.thatArePublic()
|
||||
.thatAreCalculated()
|
||||
.toCalculatedProperties()
|
||||
|
||||
// Generate the sequence of classes starting with this class and ascending through it superclasses.
|
||||
private fun Class<*>.superclassChain() = generateSequence(this, Class<*>::getSuperclass)
|
||||
|
||||
private fun Sequence<Class<*>>.withInterfaces() = flatMap {
|
||||
sequenceOf(it) + it.genericInterfaces.asSequence().map { it.asClass() }
|
||||
}
|
||||
|
||||
// Obtain the fields declared by all classes in this sequence of classes.
|
||||
private fun Sequence<Class<*>>.declaredFields() = flatMap { it.declaredFields.asSequence() }
|
||||
|
||||
@ -108,18 +123,45 @@ private fun Sequence<Class<*>>.declaredMethods() = flatMap { it.declaredMethods.
|
||||
// Map a sequence of fields by field name.
|
||||
private fun Sequence<Field>.byFieldName() = map { it.name to it }.toMap()
|
||||
|
||||
// Select only those methods that are public (and are not the "getClass" method)
|
||||
// Select only those methods that are public (and are not the "getClass" method).
|
||||
private fun Sequence<Method>.thatArePublic() = filter { it.isPublic && it.name != "getClass" }
|
||||
|
||||
// Select only those methods that are annotated with [SerializableCalculatedProperty].
|
||||
private fun Sequence<Method>.thatAreCalculated() = filter {
|
||||
it.isAnnotationPresent(SerializableCalculatedProperty::class.java)
|
||||
}
|
||||
|
||||
// Convert a sequence of calculated property methods to a map of property descriptors by property name.
|
||||
private fun Sequence<Method>.toCalculatedProperties(): Map<String, PropertyDescriptor> {
|
||||
val methodsByName = mutableMapOf<String, Method>()
|
||||
for (method in this) {
|
||||
val propertyNamedMethod = getPropertyNamedMethod(method)
|
||||
?: throw IllegalArgumentException("Calculated property method must have a name beginning with 'get' or 'is'")
|
||||
|
||||
require(propertyNamedMethod.hasValidSignature()) {
|
||||
"Calculated property name must have no parameters, and a non-void return type"
|
||||
}
|
||||
|
||||
val propertyName = propertyNamedMethod.fieldName.decapitalize()
|
||||
methodsByName.compute(propertyName) { _, existingMethod ->
|
||||
if (existingMethod == null) method
|
||||
else leastGenericBy({ genericReturnType }, existingMethod, method)
|
||||
}
|
||||
}
|
||||
return methodsByName.mapValues { (_, method) -> PropertyDescriptor(null, null, method) }
|
||||
}
|
||||
|
||||
// Select only those methods that are isX/getX/setX methods
|
||||
private fun Sequence<Method>.thatArePropertyMethods() = map { method ->
|
||||
propertyMethodRegex.find(method.name)?.let { result ->
|
||||
private fun Sequence<Method>.thatArePropertyMethods() = mapNotNull(::getPropertyNamedMethod)
|
||||
|
||||
private fun getPropertyNamedMethod(method: Method): PropertyNamedMethod? {
|
||||
return propertyMethodRegex.find(method.name)?.let { result ->
|
||||
PropertyNamedMethod(
|
||||
result.groups[2]!!.value,
|
||||
MethodClassifier.valueOf(result.groups[1]!!.value.toUpperCase()),
|
||||
method)
|
||||
}
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
// Pick only those methods whose signatures are valid, discarding the remainder without warning.
|
||||
private fun Sequence<PropertyNamedMethod>.withValidSignature() = filter { it.hasValidSignature() }
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.Field
|
||||
@ -19,9 +20,9 @@ abstract class PropertyReader {
|
||||
* Accessor for those properties of a class that have defined getter functions.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
|
||||
class PublicPropertyReader(private val readMethod: Method) : PropertyReader() {
|
||||
init {
|
||||
readMethod?.isAccessible = true
|
||||
readMethod.isAccessible = true
|
||||
}
|
||||
|
||||
private fun Method.returnsNullable(): Boolean {
|
||||
@ -47,10 +48,10 @@ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
|
||||
}
|
||||
|
||||
override fun read(obj: Any?): Any? {
|
||||
return readMethod!!.invoke(obj)
|
||||
return readMethod.invoke(obj)
|
||||
}
|
||||
|
||||
override fun isNullable(): Boolean = readMethod?.returnsNullable() ?: false
|
||||
override fun isNullable(): Boolean = readMethod.returnsNullable()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +113,6 @@ class EvolutionPropertyReader : PropertyReader() {
|
||||
* making the property accessible.
|
||||
*/
|
||||
abstract class PropertyAccessor(
|
||||
val initialPosition: Int,
|
||||
open val serializer: PropertySerializer) {
|
||||
companion object : Comparator<PropertyAccessor> {
|
||||
override fun compare(p0: PropertyAccessor?, p1: PropertyAccessor?): Int {
|
||||
@ -120,13 +120,15 @@ abstract class PropertyAccessor(
|
||||
}
|
||||
}
|
||||
|
||||
open val isCalculated get() = false
|
||||
|
||||
/**
|
||||
* Override to control how the property is set on the object.
|
||||
*/
|
||||
abstract fun set(instance: Any, obj: Any?)
|
||||
|
||||
override fun toString(): String {
|
||||
return "${serializer.name}($initialPosition)"
|
||||
return serializer.name
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,9 +137,8 @@ abstract class PropertyAccessor(
|
||||
* is serialized and deserialized via JavaBean getter and setter style methods.
|
||||
*/
|
||||
class PropertyAccessorGetterSetter(
|
||||
initialPosition: Int,
|
||||
getter: PropertySerializer,
|
||||
private val setter: Method) : PropertyAccessor(initialPosition, getter) {
|
||||
private val setter: Method) : PropertyAccessor(getter) {
|
||||
init {
|
||||
/**
|
||||
* Play nicely with Java interop, public methods aren't marked as accessible
|
||||
@ -159,16 +160,34 @@ class PropertyAccessorGetterSetter(
|
||||
* of the object the property belongs to.
|
||||
*/
|
||||
class PropertyAccessorConstructor(
|
||||
initialPosition: Int,
|
||||
override val serializer: PropertySerializer) : PropertyAccessor(initialPosition, serializer) {
|
||||
val initialPosition: Int,
|
||||
override val serializer: PropertySerializer) : PropertyAccessor(serializer) {
|
||||
/**
|
||||
* Because the property should be being set on the obejct through the constructor any
|
||||
* Because the property should be being set on the object through the constructor any
|
||||
* calls to the explicit setter should be an error.
|
||||
*/
|
||||
override fun set(instance: Any, obj: Any?) {
|
||||
NotSerializableException("Attempting to access a setter on an object being instantiated " +
|
||||
"via its constructor.")
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
"${serializer.name}($initialPosition)"
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of [PropertyAccessor] representing a calculated property of an object that is serialized
|
||||
* so that it can be used by the class carpenter, but ignored on deserialisation as there is no setter or
|
||||
* constructor parameter to receive its value.
|
||||
*
|
||||
* This will only be created for calculated properties that are accessible via no-argument methods annotated
|
||||
* with [SerializableCalculatedProperty].
|
||||
*/
|
||||
class CalculatedPropertyAccessor(override val serializer: PropertySerializer): PropertyAccessor(serializer) {
|
||||
override val isCalculated: Boolean
|
||||
get() = true
|
||||
|
||||
override fun set(instance: Any, obj: Any?) = Unit // do nothing, as it's a calculated value
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,7 +205,7 @@ abstract class PropertySerializers(
|
||||
val serializationOrder: List<PropertyAccessor>) {
|
||||
companion object {
|
||||
fun make(serializationOrder: List<PropertyAccessor>) =
|
||||
when (serializationOrder.firstOrNull()) {
|
||||
when (serializationOrder.find { !it.isCalculated }) {
|
||||
is PropertyAccessorConstructor -> PropertySerializersConstructor(serializationOrder)
|
||||
is PropertyAccessorGetterSetter -> PropertySerializersSetter(serializationOrder)
|
||||
null -> PropertySerializersNoProperties()
|
||||
@ -198,6 +217,7 @@ abstract class PropertySerializers(
|
||||
|
||||
val size get() = serializationOrder.size
|
||||
abstract val byConstructor: Boolean
|
||||
val deserializableSize = serializationOrder.count { !it.isCalculated }
|
||||
}
|
||||
|
||||
class PropertySerializersNoProperties : PropertySerializers(emptyList()) {
|
||||
|
@ -3,10 +3,7 @@ package net.corda.serialization.internal.amqp
|
||||
import com.google.common.primitives.Primitives
|
||||
import com.google.common.reflect.TypeToken
|
||||
import net.corda.core.internal.isConcreteClass
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.*
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.lang.reflect.*
|
||||
import java.lang.reflect.Field
|
||||
@ -68,12 +65,33 @@ fun <T : Any> propertiesForSerialization(
|
||||
kotlinConstructor: KFunction<T>?,
|
||||
type: Type,
|
||||
factory: SerializerFactory): PropertySerializers = PropertySerializers.make(
|
||||
if (kotlinConstructor != null) {
|
||||
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
|
||||
} else {
|
||||
propertiesForSerializationFromAbstract(type.asClass(), type, factory)
|
||||
}.sortedWith(PropertyAccessor)
|
||||
)
|
||||
getValueProperties(kotlinConstructor, type, factory)
|
||||
.addCalculatedProperties(factory, type)
|
||||
.sortedWith(PropertyAccessor))
|
||||
|
||||
fun <T : Any> getValueProperties(kotlinConstructor: KFunction<T>?, type: Type, factory: SerializerFactory)
|
||||
: List<PropertyAccessor> =
|
||||
if (kotlinConstructor != null) {
|
||||
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
|
||||
} else {
|
||||
propertiesForSerializationFromAbstract(type.asClass(), type, factory)
|
||||
}
|
||||
|
||||
private fun List<PropertyAccessor>.addCalculatedProperties(factory: SerializerFactory, type: Type)
|
||||
: List<PropertyAccessor> {
|
||||
val nonCalculated = map { it.serializer.name }.toSet()
|
||||
return this + type.asClass().calculatedPropertyDescriptors().mapNotNull { (name, descriptor) ->
|
||||
if (name in nonCalculated) null else {
|
||||
val calculatedPropertyMethod = descriptor.getter
|
||||
?: throw IllegalStateException("Property $name is not a calculated property")
|
||||
CalculatedPropertyAccessor(PropertySerializer.make(
|
||||
name,
|
||||
PublicPropertyReader(calculatedPropertyMethod),
|
||||
calculatedPropertyMethod.genericReturnType,
|
||||
factory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From a constructor, determine which properties of a class are to be serialized.
|
||||
@ -145,7 +163,7 @@ fun propertiesForSerializationFromSetters(
|
||||
properties: Map<String, PropertyDescriptor>,
|
||||
type: Type,
|
||||
factory: SerializerFactory): List<PropertyAccessor> =
|
||||
properties.asSequence().withIndex().map { (index, entry) ->
|
||||
properties.asSequence().map { entry ->
|
||||
val (name, property) = entry
|
||||
|
||||
val getter = property.getter
|
||||
@ -154,7 +172,6 @@ fun propertiesForSerializationFromSetters(
|
||||
if (getter == null || setter == null) return@map null
|
||||
|
||||
PropertyAccessorGetterSetter(
|
||||
index,
|
||||
PropertySerializer.make(
|
||||
name,
|
||||
PublicPropertyReader(getter),
|
||||
@ -191,9 +208,9 @@ private fun propertiesForSerializationFromAbstract(
|
||||
clazz: Class<*>,
|
||||
type: Type,
|
||||
factory: SerializerFactory): List<PropertyAccessor> =
|
||||
clazz.propertyDescriptors().asSequence().withIndex().map { (index, entry) ->
|
||||
clazz.propertyDescriptors().asSequence().withIndex().mapNotNull { (index, entry) ->
|
||||
val (name, property) = entry
|
||||
if (property.getter == null || property.field == null) return@map null
|
||||
if (property.getter == null || property.field == null) return@mapNotNull null
|
||||
|
||||
val getter = property.getter
|
||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||
@ -201,7 +218,7 @@ private fun propertiesForSerializationFromAbstract(
|
||||
PropertyAccessorConstructor(
|
||||
index,
|
||||
PropertySerializer.make(name, PublicPropertyReader(getter), returnType, factory))
|
||||
}.filterNotNull().toList()
|
||||
}.toList()
|
||||
|
||||
internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> =
|
||||
exploreType(type, serializerFactory).toList()
|
||||
|
@ -7,10 +7,7 @@ import net.corda.core.StubOutForDJVM
|
||||
import net.corda.core.internal.kotlinObjectInstance
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.serialization.internal.carpenter.*
|
||||
import org.apache.qpid.proton.amqp.*
|
||||
import java.io.NotSerializableException
|
||||
@ -30,7 +27,7 @@ data class CustomSerializersCacheKey(val clazz: Class<*>, val declaredType: Type
|
||||
/**
|
||||
* Factory of serializers designed to be shared across threads and invocations.
|
||||
*
|
||||
* @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal
|
||||
* @property evolutionSerializerProvider controls how evolution serializers are generated by the factory. The normal
|
||||
* use case is an [EvolutionSerializer] type is returned. However, in some scenarios, primarily testing, this
|
||||
* can be altered to fit the requirements of the test.
|
||||
* @property onlyCustomSerializers used for testing, when set will cause the factory to throw a
|
||||
@ -52,7 +49,7 @@ data class CustomSerializersCacheKey(val clazz: Class<*>, val declaredType: Type
|
||||
open class SerializerFactory(
|
||||
val whitelist: ClassWhitelist,
|
||||
val classCarpenter: ClassCarpenter,
|
||||
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
private val evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
|
||||
val fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
|
||||
private val serializersByType: MutableMap<Type, AMQPSerializer<Any>>,
|
||||
val serializersByDescriptor: MutableMap<Any, AMQPSerializer<Any>>,
|
||||
@ -64,13 +61,13 @@ open class SerializerFactory(
|
||||
@DeleteForDJVM
|
||||
constructor(whitelist: ClassWhitelist,
|
||||
classCarpenter: ClassCarpenter,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
|
||||
fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
|
||||
onlyCustomSerializers: Boolean = false
|
||||
) : this(
|
||||
whitelist,
|
||||
classCarpenter,
|
||||
evolutionSerializerGetter,
|
||||
evolutionSerializerProvider,
|
||||
fingerPrinterConstructor,
|
||||
ConcurrentHashMap(),
|
||||
ConcurrentHashMap(),
|
||||
@ -84,13 +81,13 @@ open class SerializerFactory(
|
||||
constructor(whitelist: ClassWhitelist,
|
||||
carpenterClassLoader: ClassLoader,
|
||||
lenientCarpenter: Boolean = false,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
|
||||
fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
|
||||
onlyCustomSerializers: Boolean = false
|
||||
) : this(
|
||||
whitelist,
|
||||
ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter),
|
||||
evolutionSerializerGetter,
|
||||
evolutionSerializerProvider,
|
||||
fingerPrinterConstructor,
|
||||
onlyCustomSerializers)
|
||||
|
||||
@ -98,12 +95,6 @@ open class SerializerFactory(
|
||||
|
||||
val classloader: ClassLoader get() = classCarpenter.classloader
|
||||
|
||||
private fun getEvolutionSerializer(typeNotation: TypeNotation,
|
||||
newSerializer: AMQPSerializer<Any>,
|
||||
schemas: SerializationSchemas): AMQPSerializer<Any> {
|
||||
return evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up, and manufacture if necessary, a serializer for the given type.
|
||||
*
|
||||
@ -117,11 +108,11 @@ open class SerializerFactory(
|
||||
|
||||
val declaredClass = declaredType.asClass()
|
||||
val actualType: Type = if (actualClass == null) declaredType
|
||||
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||
|
||||
val serializer = when {
|
||||
// Declared class may not be set to Collection, but actual class could be a collection.
|
||||
// In this case use of CollectionSerializer is perfectly appropriate.
|
||||
// Declared class may not be set to Collection, but actual class could be a collection.
|
||||
// In this case use of CollectionSerializer is perfectly appropriate.
|
||||
(Collection::class.java.isAssignableFrom(declaredClass) ||
|
||||
(actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) &&
|
||||
!EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
|
||||
@ -130,8 +121,8 @@ open class SerializerFactory(
|
||||
CollectionSerializer(declaredTypeAmended, this)
|
||||
}
|
||||
}
|
||||
// Declared class may not be set to Map, but actual class could be a map.
|
||||
// In this case use of MapSerializer is perfectly appropriate.
|
||||
// Declared class may not be set to Map, but actual class could be a map.
|
||||
// In this case use of MapSerializer is perfectly appropriate.
|
||||
(Map::class.java.isAssignableFrom(declaredClass) ||
|
||||
(actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> {
|
||||
val declaredTypeAmended = MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
|
||||
@ -142,8 +133,8 @@ open class SerializerFactory(
|
||||
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
|
||||
logger.trace {
|
||||
"class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " +
|
||||
"declaredType=${declaredType.typeName} " +
|
||||
"isEnum=${declaredType::class.java.isEnum}"
|
||||
"declaredType=${declaredType.typeName} " +
|
||||
"isEnum=${declaredType::class.java.isEnum}"
|
||||
}
|
||||
|
||||
serializersByType.computeIfAbsent(actualClass ?: declaredClass) {
|
||||
@ -203,33 +194,86 @@ open class SerializerFactory(
|
||||
* if not, use the [ClassCarpenter] to generate a class to use in its place.
|
||||
*/
|
||||
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
||||
val metaSchema = CarpenterMetaSchema.newInstance()
|
||||
for (typeNotation in schemaAndDescriptor.schemas.schema.types) {
|
||||
logger.trace { "descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}" }
|
||||
val requiringCarpentry = schemaAndDescriptor.schemas.schema.types.mapNotNull { typeNotation ->
|
||||
try {
|
||||
val serialiser = processSchemaEntry(typeNotation)
|
||||
// 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
|
||||
// instance of the class, as such we need to build an EvolutionSerializer
|
||||
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
|
||||
logger.trace { "typeNotation=${typeNotation.name} action=\"requires Evolution\"" }
|
||||
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
|
||||
}
|
||||
getOrRegisterSerializer(schemaAndDescriptor, typeNotation)
|
||||
return@mapNotNull null
|
||||
} catch (e: ClassNotFoundException) {
|
||||
if (sentinel) {
|
||||
logger.error("typeNotation=${typeNotation.name} error=\"after Carpentry attempt failed to load\"")
|
||||
throw e
|
||||
}
|
||||
else {
|
||||
logger.trace { "typeNotation=\"${typeNotation.name}\" action=\"carpentry required\"" }
|
||||
}
|
||||
metaSchema.buildFor(typeNotation, classloader)
|
||||
logger.trace { "typeNotation=\"${typeNotation.name}\" action=\"carpentry required\"" }
|
||||
return@mapNotNull typeNotation
|
||||
}
|
||||
}.toList()
|
||||
|
||||
if (requiringCarpentry.isEmpty()) return
|
||||
|
||||
runCarpentry(schemaAndDescriptor, CarpenterMetaSchema.buildWith(classloader, requiringCarpentry))
|
||||
}
|
||||
|
||||
private fun getOrRegisterSerializer(schemaAndDescriptor: FactorySchemaAndDescriptor, typeNotation: TypeNotation) {
|
||||
logger.trace { "descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}" }
|
||||
val serialiser = processSchemaEntry(typeNotation)
|
||||
|
||||
// if we just successfully built a serializer for the type but the type fingerprint
|
||||
// doesn't match that of the serialised object then we may be dealing with different
|
||||
// instance of the class, and such we need to build an EvolutionSerializer
|
||||
if (serialiser.typeDescriptor == typeNotation.descriptor.name) return
|
||||
|
||||
logger.trace { "typeNotation=${typeNotation.name} action=\"requires Evolution\"" }
|
||||
evolutionSerializerProvider.getEvolutionSerializer(this, typeNotation, serialiser, schemaAndDescriptor.schemas)
|
||||
}
|
||||
|
||||
private fun processSchemaEntry(typeNotation: TypeNotation) = when (typeNotation) {
|
||||
// java.lang.Class (whether a class or interface)
|
||||
is CompositeType -> {
|
||||
logger.trace("typeNotation=${typeNotation.name} amqpType=CompositeType")
|
||||
processCompositeType(typeNotation)
|
||||
}
|
||||
// Collection / Map, possibly with generics
|
||||
is RestrictedType -> {
|
||||
logger.trace("typeNotation=${typeNotation.name} amqpType=RestrictedType")
|
||||
processRestrictedType(typeNotation)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: class loader logic, and compare the schema.
|
||||
private fun processRestrictedType(typeNotation: RestrictedType) =
|
||||
get(null, typeForName(typeNotation.name, classloader))
|
||||
|
||||
private fun processCompositeType(typeNotation: CompositeType): AMQPSerializer<Any> {
|
||||
// TODO: class loader logic, and compare the schema.
|
||||
val type = typeForName(typeNotation.name, classloader)
|
||||
return get(type.asClass(), type)
|
||||
}
|
||||
|
||||
private fun typeForName(name: String, classloader: ClassLoader): Type = when {
|
||||
name.endsWith("[]") -> {
|
||||
val elementType = typeForName(name.substring(0, name.lastIndex - 1), classloader)
|
||||
if (elementType is ParameterizedType || elementType is GenericArrayType) {
|
||||
DeserializedGenericArrayType(elementType)
|
||||
} else if (elementType is Class<*>) {
|
||||
java.lang.reflect.Array.newInstance(elementType, 0).javaClass
|
||||
} else {
|
||||
throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name")
|
||||
}
|
||||
}
|
||||
|
||||
if (metaSchema.isNotEmpty()) {
|
||||
runCarpentry(schemaAndDescriptor, metaSchema)
|
||||
}
|
||||
name.endsWith("[p]") -> // There is no need to handle the ByteArray case as that type is coercible automatically
|
||||
// to the binary type and is thus handled by the main serializer and doesn't need a
|
||||
// special case for a primitive array of bytes
|
||||
when (name) {
|
||||
"int[p]" -> IntArray::class.java
|
||||
"char[p]" -> CharArray::class.java
|
||||
"boolean[p]" -> BooleanArray::class.java
|
||||
"float[p]" -> FloatArray::class.java
|
||||
"double[p]" -> DoubleArray::class.java
|
||||
"short[p]" -> ShortArray::class.java
|
||||
"long[p]" -> LongArray::class.java
|
||||
else -> throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name")
|
||||
}
|
||||
else -> DeserializedParameterizedType.make(name, classloader)
|
||||
}
|
||||
|
||||
@StubOutForDJVM
|
||||
@ -251,31 +295,6 @@ open class SerializerFactory(
|
||||
processSchema(schemaAndDescriptor, true)
|
||||
}
|
||||
|
||||
private fun processSchemaEntry(typeNotation: TypeNotation) = when (typeNotation) {
|
||||
// java.lang.Class (whether a class or interface)
|
||||
is CompositeType -> {
|
||||
logger.trace("typeNotation=${typeNotation.name} amqpType=CompositeType")
|
||||
processCompositeType(typeNotation)
|
||||
}
|
||||
// Collection / Map, possibly with generics
|
||||
is RestrictedType -> {
|
||||
logger.trace("typeNotation=${typeNotation.name} amqpType=RestrictedType")
|
||||
processRestrictedType(typeNotation)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: class loader logic, and compare the schema.
|
||||
private fun processRestrictedType(typeNotation: RestrictedType) = get(null,
|
||||
typeForName(typeNotation.name, classloader))
|
||||
|
||||
private fun processCompositeType(typeNotation: CompositeType): AMQPSerializer<Any> {
|
||||
// TODO: class loader logic, and compare the schema.
|
||||
val type = typeForName(typeNotation.name, classloader)
|
||||
return get(
|
||||
type.asClass(),
|
||||
type)
|
||||
}
|
||||
|
||||
private fun makeClassSerializer(
|
||||
clazz: Class<*>,
|
||||
type: Type,
|
||||
@ -352,6 +371,9 @@ open class SerializerFactory(
|
||||
return MapSerializer(declaredType, this)
|
||||
}
|
||||
|
||||
fun registerByDescriptor(name: Symbol, serializerCreator: () -> AMQPSerializer<Any>): AMQPSerializer<Any> =
|
||||
serializersByDescriptor.computeIfAbsent(name) { _ -> serializerCreator() }
|
||||
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
|
||||
@ -405,35 +427,6 @@ open class SerializerFactory(
|
||||
is TypeVariable<*> -> "?"
|
||||
else -> throw AMQPNotSerializableException(type, "Unable to render type $type to a string.")
|
||||
}
|
||||
|
||||
private fun typeForName(name: String, classloader: ClassLoader): Type {
|
||||
return if (name.endsWith("[]")) {
|
||||
val elementType = typeForName(name.substring(0, name.lastIndex - 1), classloader)
|
||||
if (elementType is ParameterizedType || elementType is GenericArrayType) {
|
||||
DeserializedGenericArrayType(elementType)
|
||||
} else if (elementType is Class<*>) {
|
||||
java.lang.reflect.Array.newInstance(elementType, 0).javaClass
|
||||
} else {
|
||||
throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name")
|
||||
}
|
||||
} else if (name.endsWith("[p]")) {
|
||||
// There is no need to handle the ByteArray case as that type is coercible automatically
|
||||
// to the binary type and is thus handled by the main serializer and doesn't need a
|
||||
// special case for a primitive array of bytes
|
||||
when (name) {
|
||||
"int[p]" -> IntArray::class.java
|
||||
"char[p]" -> CharArray::class.java
|
||||
"boolean[p]" -> BooleanArray::class.java
|
||||
"float[p]" -> FloatArray::class.java
|
||||
"double[p]" -> DoubleArray::class.java
|
||||
"short[p]" -> ShortArray::class.java
|
||||
"long[p]" -> LongArray::class.java
|
||||
else -> throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name")
|
||||
}
|
||||
} else {
|
||||
DeserializedParameterizedType.make(name, classloader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AnyType : WildcardType {
|
||||
@ -443,4 +436,4 @@ open class SerializerFactory(
|
||||
|
||||
override fun toString(): String = "?"
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,11 @@ data class CarpenterMetaSchema(
|
||||
val dependencies: MutableMap<String, Pair<TypeNotation, MutableList<String>>>,
|
||||
val dependsOn: MutableMap<String, MutableList<String>>) {
|
||||
companion object CarpenterSchemaConstructor {
|
||||
fun buildWith(classLoader: ClassLoader, types: List<TypeNotation>) =
|
||||
newInstance().apply {
|
||||
types.forEach { buildFor(it, classLoader) }
|
||||
}
|
||||
|
||||
fun newInstance(): CarpenterMetaSchema {
|
||||
return CarpenterMetaSchema(mutableListOf(), mutableMapOf(), mutableMapOf())
|
||||
}
|
||||
|
@ -0,0 +1,107 @@
|
||||
package net.corda.serialization.internal.carpenter;
|
||||
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty;
|
||||
import net.corda.core.serialization.SerializationContext;
|
||||
import net.corda.core.serialization.SerializationFactory;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.*;
|
||||
import net.corda.serialization.internal.amqp.Schema;
|
||||
import net.corda.testing.core.SerializationEnvironmentRule;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactoryNoEvolution;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JavaCalculatedValuesToClassCarpenterTest extends AmqpCarpenterBase {
|
||||
public JavaCalculatedValuesToClassCarpenterTest() {
|
||||
super(AllWhitelist.INSTANCE);
|
||||
}
|
||||
|
||||
public interface Parent {
|
||||
@SerializableCalculatedProperty
|
||||
int getDoubled();
|
||||
}
|
||||
|
||||
public static final class C implements Parent {
|
||||
private final int i;
|
||||
|
||||
public C(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
@SerializableCalculatedProperty
|
||||
public String getSquared() {
|
||||
return Integer.toString(i * i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDoubled() {
|
||||
return i * 2;
|
||||
}
|
||||
|
||||
public int getI() {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
public final SerializationEnvironmentRule serializationEnvironmentRule = new SerializationEnvironmentRule();
|
||||
private SerializationContext context;
|
||||
|
||||
@Before
|
||||
public void initSerialization() {
|
||||
SerializationFactory factory = serializationEnvironmentRule.getSerializationFactory();
|
||||
context = factory.getDefaultContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculatedValues() throws Exception {
|
||||
SerializerFactory factory = testDefaultFactoryNoEvolution();
|
||||
SerializedBytes<C> serialized = serialise(new C(2));
|
||||
ObjectAndEnvelope<C> objAndEnv = new DeserializationInput(factory)
|
||||
.deserializeAndReturnEnvelope(serialized, C.class, context);
|
||||
|
||||
C amqpObj = objAndEnv.getObj();
|
||||
Schema schema = objAndEnv.getEnvelope().getSchema();
|
||||
|
||||
assertEquals(2, amqpObj.getI());
|
||||
assertEquals("4", amqpObj.getSquared());
|
||||
assertEquals(2, schema.getTypes().size());
|
||||
assertTrue(schema.getTypes().get(0) instanceof CompositeType);
|
||||
|
||||
CompositeType concrete = (CompositeType) schema.getTypes().get(0);
|
||||
assertEquals(3, concrete.getFields().size());
|
||||
assertEquals("doubled", concrete.getFields().get(0).getName());
|
||||
assertEquals("int", concrete.getFields().get(0).getType());
|
||||
assertEquals("i", concrete.getFields().get(1).getName());
|
||||
assertEquals("int", concrete.getFields().get(1).getType());
|
||||
assertEquals("squared", concrete.getFields().get(2).getName());
|
||||
assertEquals("string", concrete.getFields().get(2).getType());
|
||||
|
||||
assertEquals(0, AMQPSchemaExtensions.carpenterSchema(schema, ClassLoader.getSystemClassLoader()).getSize());
|
||||
Schema mangledSchema = ClassCarpenterTestUtilsKt.mangleNames(schema, singletonList(C.class.getTypeName()));
|
||||
CarpenterMetaSchema l2 = AMQPSchemaExtensions.carpenterSchema(mangledSchema, ClassLoader.getSystemClassLoader());
|
||||
String mangledClassName = ClassCarpenterTestUtilsKt.mangleName(C.class.getTypeName());
|
||||
|
||||
assertEquals(1, l2.getSize());
|
||||
net.corda.serialization.internal.carpenter.Schema carpenterSchema = l2.getCarpenterSchemas().stream()
|
||||
.filter(s -> s.getName().equals(mangledClassName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No schema found for mangled class name " + mangledClassName));
|
||||
|
||||
Class<?> pinochio = new ClassCarpenterImpl(AllWhitelist.INSTANCE).build(carpenterSchema);
|
||||
Object p = pinochio.getConstructors()[0].newInstance(4, 2, "4");
|
||||
|
||||
assertEquals(pinochio.getMethod("getI").invoke(p), amqpObj.getI());
|
||||
assertEquals(pinochio.getMethod("getSquared").invoke(p), amqpObj.getSquared());
|
||||
assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.getDoubled());
|
||||
|
||||
Parent upcast = (Parent) p;
|
||||
assertEquals(upcast.getDoubled(), amqpObj.getDoubled());
|
||||
}
|
||||
}
|
@ -3,12 +3,12 @@ package net.corda.serialization.internal.amqp
|
||||
import java.io.NotSerializableException
|
||||
|
||||
/**
|
||||
* An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a
|
||||
* An implementation of [EvolutionSerializerProvider] that disables all evolution within a
|
||||
* [SerializerFactory]. This is most useful in testing where it is known that evolution should not be
|
||||
* occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This
|
||||
* prevents that by simply throwing an exception whenever such a serializer is requested.
|
||||
*/
|
||||
class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() {
|
||||
object FailIfEvolutionAttempted : EvolutionSerializerProvider {
|
||||
override fun getEvolutionSerializer(factory: SerializerFactory,
|
||||
typeNotation: TypeNotation,
|
||||
newSerializer: AMQPSerializer<Any>,
|
@ -42,7 +42,7 @@ class FingerPrinterTestingTests {
|
||||
val factory = SerializerFactory(
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter = EvolutionSerializerGetterTesting(),
|
||||
evolutionSerializerProvider = FailIfEvolutionAttempted,
|
||||
fingerPrinterConstructor = { _ -> FingerPrinterTesting() })
|
||||
|
||||
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
@ -61,4 +62,62 @@ class RoundTripTests {
|
||||
val newC = DeserializationInput(factory).deserialize(bytes)
|
||||
newC.copy(l = (newC.l + "d"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculatedValues() {
|
||||
data class C(val i: Int) {
|
||||
@get:SerializableCalculatedProperty
|
||||
val squared = i * i
|
||||
}
|
||||
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(C(2))
|
||||
val deserialized = DeserializationInput(factory).deserialize(bytes)
|
||||
assertThat(deserialized.squared).isEqualTo(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculatedFunction() {
|
||||
class C {
|
||||
var i: Int = 0
|
||||
@SerializableCalculatedProperty
|
||||
fun getSquared() = i * i
|
||||
}
|
||||
|
||||
val instance = C().apply { i = 2 }
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(instance)
|
||||
val deserialized = DeserializationInput(factory).deserialize(bytes)
|
||||
assertThat(deserialized.getSquared()).isEqualTo(4)
|
||||
}
|
||||
|
||||
interface I {
|
||||
@get:SerializableCalculatedProperty
|
||||
val squared: Int
|
||||
}
|
||||
|
||||
@Test
|
||||
fun inheritedCalculatedFunction() {
|
||||
class C: I {
|
||||
var i: Int = 0
|
||||
override val squared get() = i * i
|
||||
}
|
||||
|
||||
val instance = C().apply { i = 2 }
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(instance)
|
||||
val deserialized = DeserializationInput(factory).deserialize(bytes) as I
|
||||
assertThat(deserialized.squared).isEqualTo(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun inheritedCalculatedFunctionIsNotCalculated() {
|
||||
class C(override val squared: Int): I
|
||||
|
||||
val instance = C(2)
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(instance)
|
||||
val deserialized = DeserializationInput(factory).deserialize(bytes) as I
|
||||
assertThat(deserialized.squared).isEqualTo(2)
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
return SerializerFactory(
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter = EvolutionSerializerGetterTesting()
|
||||
evolutionSerializerProvider = FailIfEvolutionAttempted
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ fun testDefaultFactoryNoEvolution(): SerializerFactory {
|
||||
return SerializerFactory(
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter = EvolutionSerializerGetterTesting())
|
||||
evolutionSerializerProvider = FailIfEvolutionAttempted)
|
||||
}
|
||||
|
||||
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
||||
|
@ -0,0 +1,101 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.SerializableCalculatedProperty
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.CompositeType
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CalculatedValuesToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
|
||||
interface Parent {
|
||||
@get:SerializableCalculatedProperty
|
||||
val doubled: Int
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculatedValues() {
|
||||
data class C(val i: Int): Parent {
|
||||
@get:SerializableCalculatedProperty
|
||||
val squared = (i * i).toString()
|
||||
|
||||
override val doubled get() = i * 2
|
||||
}
|
||||
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(C(2)))
|
||||
val amqpObj = obj.obj
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(2, amqpObj.i)
|
||||
assertEquals("4", amqpObj.squared)
|
||||
assertEquals(2, serSchema.types.size)
|
||||
require(serSchema.types[0] is CompositeType)
|
||||
|
||||
val concrete = serSchema.types[0] as CompositeType
|
||||
assertEquals(3, concrete.fields.size)
|
||||
assertEquals("doubled", concrete.fields[0].name)
|
||||
assertEquals("int", concrete.fields[0].type)
|
||||
assertEquals("i", concrete.fields[1].name)
|
||||
assertEquals("int", concrete.fields[1].type)
|
||||
assertEquals("squared", concrete.fields[2].name)
|
||||
assertEquals("string", concrete.fields[2].type)
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
assertEquals(0, l1.size)
|
||||
val mangleSchema = serSchema.mangleNames(listOf((classTestName("C"))))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("C"))
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }!!
|
||||
|
||||
val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = pinochio.constructors[0].newInstance(4, 2, "4")
|
||||
|
||||
assertEquals(pinochio.getMethod("getI").invoke(p), amqpObj.i)
|
||||
assertEquals(pinochio.getMethod("getSquared").invoke(p), amqpObj.squared)
|
||||
assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.doubled)
|
||||
|
||||
val upcast = p as Parent
|
||||
assertEquals(upcast.doubled, amqpObj.doubled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun implementingClassDoesNotCalculateValue() {
|
||||
class C(override val doubled: Int): Parent
|
||||
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(C(5)))
|
||||
val amqpObj = obj.obj
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(2, serSchema.types.size)
|
||||
require(serSchema.types[0] is CompositeType)
|
||||
|
||||
val concrete = serSchema.types[0] as CompositeType
|
||||
assertEquals(1, concrete.fields.size)
|
||||
assertEquals("doubled", concrete.fields[0].name)
|
||||
assertEquals("int", concrete.fields[0].type)
|
||||
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
assertEquals(0, l1.size)
|
||||
val mangleSchema = serSchema.mangleNames(listOf((classTestName("C"))))
|
||||
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
val aName = mangleName(classTestName("C"))
|
||||
|
||||
assertEquals(1, l2.size)
|
||||
val aSchema = l2.carpenterSchemas.find { it.name == aName }!!
|
||||
|
||||
val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema)
|
||||
val p = pinochio.constructors[0].newInstance(5)
|
||||
|
||||
assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.doubled)
|
||||
|
||||
val upcast = p as Parent
|
||||
assertEquals(upcast.doubled, amqpObj.doubled)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.serialization.internal.amqp.Field
|
||||
import net.corda.serialization.internal.amqp.Schema
|
||||
@ -47,7 +48,7 @@ open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
||||
var cc = ClassCarpenterImpl(whitelist = whitelist)
|
||||
var factory = SerializerFactoryExternalCarpenter(cc)
|
||||
|
||||
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
||||
fun <T: Any> serialise(obj: T): SerializedBytes<T> = SerializationOutput(factory).serialize(obj)
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||
}
|
||||
|
@ -30,9 +30,7 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
val b = B(A(testA), testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
|
||||
require(obj.obj is B)
|
||||
|
||||
val amqpObj = obj.obj as B
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(testB, amqpObj.b)
|
||||
assertEquals(testA, amqpObj.a.a)
|
||||
@ -92,8 +90,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A")))
|
||||
|
||||
require(obj.obj is B)
|
||||
|
||||
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
}
|
||||
|
||||
@ -111,8 +107,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
val b = B(A(testA), testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
|
||||
require(obj.obj is B)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B")))
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
@ -138,9 +132,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
|
||||
val b = B(A(testA), testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
|
||||
require(obj.obj is B)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
|
||||
@ -198,8 +189,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
val c = C(B(testA, testB), testC)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
|
||||
|
||||
require(obj.obj is C)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
|
||||
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
@ -224,8 +213,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
val c = C(B(testA, testB), testC)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
|
||||
|
||||
require(obj.obj is C)
|
||||
|
||||
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
|
||||
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
@ -250,8 +237,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) {
|
||||
val c = C(B(testA, testB), testC)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
|
||||
|
||||
require(obj.obj is C)
|
||||
|
||||
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
|
||||
TestMetaCarpenter(carpenterSchema.carpenterSchema(
|
||||
ClassLoader.getSystemClassLoader()), ClassCarpenterImpl(whitelist = AllWhitelist))
|
||||
|
@ -44,7 +44,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
|
||||
assertEquals(testJ, a.j)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
assertTrue(obj.obj is A)
|
||||
val serSchema = obj.envelope.schema
|
||||
assertEquals(2, serSchema.types.size)
|
||||
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
|
||||
@ -84,8 +83,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
assertTrue(obj.obj is A)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(2, serSchema.types.size)
|
||||
@ -128,8 +125,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
val a = A(testI, testII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
assertTrue(obj.obj is A)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(3, serSchema.types.size)
|
||||
@ -176,8 +171,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
val a = A(testI, testIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
assertTrue(obj.obj is A)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
assertEquals(3, serSchema.types.size)
|
||||
@ -225,8 +218,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
val b = B(a, testIIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
|
||||
assertTrue(obj.obj is B)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
// Expected classes are
|
||||
@ -281,8 +272,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
val b = B(a, testIIII)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||
|
||||
assertTrue(obj.obj is B)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
// The classes we're expecting to find:
|
||||
@ -305,8 +294,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) {
|
||||
val a = A(testI)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
assertTrue(obj.obj is A)
|
||||
|
||||
val serSchema = obj.envelope.schema
|
||||
|
||||
// The classes we're expecting to find:
|
||||
|
@ -21,8 +21,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi
|
||||
val a = A(testA, testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(testA, amqpObj.a)
|
||||
assertEquals(testB, amqpObj.b)
|
||||
@ -65,8 +64,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi
|
||||
val a = A(testA, testB)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(testA, amqpObj.a)
|
||||
assertEquals(testB, amqpObj.b)
|
||||
|
@ -17,9 +17,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh
|
||||
val test = 10
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
@ -53,9 +51,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh
|
||||
val a = A(test)
|
||||
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
@ -83,9 +79,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh
|
||||
val test = 10L
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
@ -118,9 +112,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh
|
||||
val test = 10.toShort()
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
@ -153,9 +145,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh
|
||||
val test = 10.0
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
@ -188,9 +178,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh
|
||||
val test = 10.0F
|
||||
val a = A(test)
|
||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||
|
||||
require(obj.obj is A)
|
||||
val amqpObj = obj.obj as A
|
||||
val amqpObj = obj.obj
|
||||
|
||||
assertEquals(test, amqpObj.a)
|
||||
assertEquals(1, obj.envelope.schema.types.size)
|
||||
|
Loading…
Reference in New Issue
Block a user