Merge pull request #2317 from corda/kat/feature/setterConstructAMQP

CORDA-902 - AMQP Setter Construction when empty / no constructor
This commit is contained in:
Katelyn Baker 2018-01-05 16:37:23 +00:00 committed by GitHub
commit 37e0830b7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 421 additions and 39 deletions

View File

@ -70,7 +70,7 @@ class CorDappCustomSerializer(
data.withDescribed(descriptor) { data.withDescribed(descriptor) {
data.withList { data.withList {
for (property in proxySerializer.propertySerializers) { for (property in proxySerializer.propertySerializers.getters) {
property.writeProperty(proxy, this, output) property.writeProperty(proxy, this, output)
} }
} }

View File

@ -132,7 +132,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) { override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) {
val proxy = toProxy(obj) val proxy = toProxy(obj)
data.withList { data.withList {
for (property in proxySerializer.propertySerializers) { for (property in proxySerializer.propertySerializers.getters) {
property.writeProperty(proxy, this, output) property.writeProperty(proxy, this, output)
} }
} }

View File

@ -20,7 +20,7 @@ class EvolutionSerializer(
override val kotlinConstructor: KFunction<Any>?) : ObjectSerializer(clazz, factory) { override val kotlinConstructor: KFunction<Any>?) : ObjectSerializer(clazz, factory) {
// explicitly set as empty to indicate it's unused by this type of serializer // explicitly set as empty to indicate it's unused by this type of serializer
override val propertySerializers: Collection<PropertySerializer> = emptyList() override val propertySerializers = ConstructorDestructorMethods (emptyList(), emptyList())
/** /**
* Represents a parameter as would be passed to the constructor of the class as it was * Represents a parameter as would be passed to the constructor of the class as it was

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
@ -21,7 +22,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val logger = contextLogger() private val logger = contextLogger()
} }
open internal val propertySerializers: Collection<PropertySerializer> by lazy { open internal val propertySerializers: ConstructorDestructorMethods by lazy {
propertiesForSerialization(kotlinConstructor, clazz, factory) propertiesForSerialization(kotlinConstructor, clazz, factory)
} }
@ -37,7 +38,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
for (iface in interfaces) { for (iface in interfaces) {
output.requireSerializer(iface) output.requireSerializer(iface)
} }
for (property in propertySerializers) { for (property in propertySerializers.getters) {
property.writeClassInfo(output) property.writeClassInfo(output)
} }
} }
@ -48,7 +49,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
data.withDescribed(typeNotation.descriptor) { data.withDescribed(typeNotation.descriptor) {
// Write list // Write list
withList { withList {
for (property in propertySerializers) { for (property in propertySerializers.getters) {
property.writeProperty(obj, this, output) property.writeProperty(obj, this, output)
} }
} }
@ -60,23 +61,59 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
schemas: SerializationSchemas, schemas: SerializationSchemas,
input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) { input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) {
if (obj is List<*>) { if (obj is List<*>) {
if (obj.size > propertySerializers.size) { if (obj.size > propertySerializers.getters.size) {
throw NotSerializableException("Too many properties in described type $typeName") throw NotSerializableException("Too many properties in described type $typeName")
} }
val params = obj.zip(propertySerializers).map { it.second.readProperty(it.first, schemas, input) }
construct(params) return if (propertySerializers.setters.isEmpty()) {
readObjectBuildViaConstructor(obj, schemas, input)
} else {
readObjectBuildViaSetters(obj, schemas, input)
}
} else throw NotSerializableException("Body of described type is unexpected $obj") } else throw NotSerializableException("Body of described type is unexpected $obj")
} }
private fun readObjectBuildViaConstructor(
obj: List<*>,
schemas: SerializationSchemas,
input: DeserializationInput) : Any = ifThrowsAppend({ clazz.typeName }){
logger.trace { "Calling construction based construction for ${clazz.typeName}" }
return construct(obj.zip(propertySerializers.getters).map { it.second.readProperty(it.first, schemas, input) })
}
private fun readObjectBuildViaSetters(
obj: List<*>,
schemas: SerializationSchemas,
input: DeserializationInput) : Any = ifThrowsAppend({ clazz.typeName }){
logger.trace { "Calling setter based construction for ${clazz.typeName}" }
val instance : Any = javaConstructor?.newInstance() ?: throw NotSerializableException (
"Failed to instantiate instance of object $clazz")
// read the properties out of the serialised form
val propertiesFromBlob = obj
.zip(propertySerializers.getters)
.map { it.second.readProperty(it.first, schemas, input) }
// one by one take a property and invoke the setter on the class
propertySerializers.setters.zip(propertiesFromBlob).forEach {
it.first?.invoke(instance, *listOf(it.second).toTypedArray())
}
return instance
}
private fun generateFields(): List<Field> { private fun generateFields(): List<Field> {
return propertySerializers.map { Field(it.name, it.type, it.requires, it.default, null, it.mandatory, false) } return propertySerializers.getters.map {
Field(it.name, it.type, it.requires, it.default, null, it.mandatory, false)
}
} }
private fun generateProvides(): List<String> = interfaces.map { nameForType(it) } private fun generateProvides(): List<String> = interfaces.map { nameForType(it) }
fun construct(properties: List<Any?>): Any { fun construct(properties: List<Any?>): Any {
logger.trace { "Calling constructor: '$javaConstructor' with properties '$properties'" }
logger.debug { "Calling constructor: '$javaConstructor' with properties '$properties'" }
return javaConstructor?.newInstance(*properties.toTypedArray()) ?: return javaConstructor?.newInstance(*properties.toTypedArray()) ?:
throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.") throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.")

View File

@ -419,9 +419,12 @@ private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssign
private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher { private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
// Hash the class + properties + interfaces // Hash the class + properties + interfaces
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type") val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).fold(hasher.putUnencodedChars(name)) { orig, prop -> propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory).putUnencodedChars(prop.name).putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH) .fold(hasher.putUnencodedChars(name)) { orig, prop ->
} fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory)
.putUnencodedChars(prop.name)
.putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) } interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
return hasher return hasher
} }

View File

@ -2,12 +2,14 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.primitives.Primitives import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken import com.google.common.reflect.TypeToken
import io.netty.util.internal.EmptyArrays
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.beans.IndexedPropertyDescriptor import java.beans.IndexedPropertyDescriptor
import java.beans.Introspector import java.beans.Introspector
import java.beans.PropertyDescriptor
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.* import java.lang.reflect.*
import java.util.* import java.util.*
@ -26,6 +28,10 @@ import kotlin.reflect.jvm.javaType
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ConstructorForDeserialization annotation class ConstructorForDeserialization
data class ConstructorDestructorMethods(
val getters : Collection<PropertySerializer>,
val setters : Collection<Method?>)
/** /**
* Code for finding the constructor we will use for deserialization. * Code for finding the constructor we will use for deserialization.
* *
@ -67,18 +73,30 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
* Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters have * Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters have
* names accessible via reflection. * names accessible via reflection.
*/ */
internal fun <T : Any> propertiesForSerialization(kotlinConstructor: KFunction<T>?, type: Type, factory: SerializerFactory): Collection<PropertySerializer> { internal fun <T : Any> propertiesForSerialization(kotlinConstructor: KFunction<T>?, type: Type, factory: SerializerFactory): ConstructorDestructorMethods {
val clazz = type.asClass()!! val clazz = type.asClass()!!
return if (kotlinConstructor != null) propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) else propertiesForSerializationFromAbstract(clazz, type, factory) return if (kotlinConstructor != null) propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) else propertiesForSerializationFromAbstract(clazz, type, factory)
} }
fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers)) fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructor: KFunction<T>, type: Type, factory: SerializerFactory): Collection<PropertySerializer> { private fun <T : Any> propertiesForSerializationFromConstructor(
kotlinConstructor: KFunction<T>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.groupBy { it.name }.mapValues { it.value[0] } val properties = Introspector.getBeanInfo(clazz).propertyDescriptors
.filter { it.name != "class" }
.groupBy { it.name }
.mapValues { it.value[0] }
if (properties.isNotEmpty() && kotlinConstructor.parameters.isEmpty()) {
return propertiesForSerializationFromSetters(properties, type, factory)
}
val rc: MutableList<PropertySerializer> = ArrayList(kotlinConstructor.parameters.size) val rc: MutableList<PropertySerializer> = ArrayList(kotlinConstructor.parameters.size)
for (param in kotlinConstructor.parameters) { for (param in kotlinConstructor.parameters) {
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.") val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
@ -103,7 +121,37 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}") throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
} }
} }
return rc
return ConstructorDestructorMethods(rc, emptyList())
}
/**
* If we determine a class has a constructor that takes no parameters then check for pairs of getters / setters
* and use those
*/
private fun propertiesForSerializationFromSetters(
properties : Map<String, PropertyDescriptor>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
val getters : MutableList<PropertySerializer> = ArrayList(properties.size)
val setters : MutableList<Method?> = ArrayList(properties.size)
properties.forEach { property ->
val getter: Method? = property.value.readMethod
val setter: Method? = property.value.writeMethod
if (getter == null || setter == null) return@forEach
// NOTE: There is no need to check return and parameter types vs the underlying type for
// the getter / setter vs property as if there is a difference then that property isn't reported
// by the BEAN inspector and thus we don't consider that case here
getters += PropertySerializer.make(property.key, getter, resolveTypeVariables(getter.genericReturnType, type),
factory)
setters += setter
}
return ConstructorDestructorMethods(getters, setters)
} }
private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean { private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean {
@ -111,7 +159,7 @@ private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawG
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType) return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
} }
private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): Collection<PropertySerializer> { private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): ConstructorDestructorMethods {
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.sortedBy { it.name }.filterNot { it is IndexedPropertyDescriptor } val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.sortedBy { it.name }.filterNot { it is IndexedPropertyDescriptor }
val rc: MutableList<PropertySerializer> = ArrayList(properties.size) val rc: MutableList<PropertySerializer> = ArrayList(properties.size)
@ -121,7 +169,7 @@ private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type,
val returnType = resolveTypeVariables(getter.genericReturnType, type) val returnType = resolveTypeVariables(getter.genericReturnType, type)
rc += PropertySerializer.make(property.name, getter, returnType, factory) rc += PropertySerializer.make(property.name, getter, returnType, factory)
} }
return rc return ConstructorDestructorMethods(rc, emptyList())
} }
internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> { internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> {

View File

@ -8,6 +8,10 @@ import java.nio.ByteBuffer
import java.util.* import java.util.*
import kotlin.collections.LinkedHashSet import kotlin.collections.LinkedHashSet
data class BytesAndSchemas<T : Any>(
val obj: SerializedBytes<T>,
val schema: Schema,
val transformsSchema: TransformsSchema)
/** /**
* Main entry point for serializing an object to AMQP. * Main entry point for serializing an object to AMQP.
@ -35,6 +39,18 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
} }
} }
@Throws(NotSerializableException::class)
fun <T : Any> serializeAndReturnSchema(obj: T): BytesAndSchemas<T> {
try {
val blob = _serialize(obj)
val schema = Schema(schemaHistory.toList())
return BytesAndSchemas(blob, schema, TransformsSchema.build(schema, serializerFactory))
} finally {
andFinally()
}
}
internal fun andFinally() { internal fun andFinally() {
objectHistory.clear() objectHistory.clear()
serializerHistory.clear() serializerHistory.clear()

View File

@ -24,7 +24,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
try { try {
val constructor = constructorForDeserialization(obj.javaClass) val constructor = constructorForDeserialization(obj.javaClass)
val props = propertiesForSerialization(constructor, obj.javaClass, factory) val props = propertiesForSerialization(constructor, obj.javaClass, factory)
for (prop in props) { for (prop in props.getters) {
extraProperties[prop.name] = prop.readMethod!!.invoke(obj) extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
} }
} catch (e: NotSerializableException) { } catch (e: NotSerializableException) {

View File

@ -0,0 +1,295 @@
package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.NotSerializableException;
public class SetterConstructorTests {
static class C {
private int a;
private int b;
private int c;
public int getA() { return a; }
public int getB() { return b; }
public int getC() { return c; }
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
public void setC(int c) { this.c = c; }
}
static class C2 {
private int a;
private int b;
private int c;
public int getA() { return a; }
public int getB() { return b; }
public int getC() { return c; }
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
}
static class C3 {
private int a;
private int b;
private int c;
public int getA() { return a; }
public int getC() { return c; }
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
public void setC(int c) { this.c = c; }
}
static class C4 {
private int a;
private int b;
private int c;
public int getA() { return a; }
protected int getB() { return b; }
public int getC() { return c; }
private void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
public void setC(int c) { this.c = c; }
}
static class Inner1 {
private String a;
public Inner1(String a) { this.a = a; }
public String getA() { return this.a; }
}
static class Inner2 {
private Double a;
public Double getA() { return this.a; }
public void setA(Double a) { this.a = a; }
}
static class Outer {
private Inner1 a;
private String b;
private Inner2 c;
public Inner1 getA() { return a; }
public String getB() { return b; }
public Inner2 getC() { return c; }
public void setA(Inner1 a) { this.a = a; }
public void setB(String b) { this.b = b; }
public void setC(Inner2 c) { this.c = c; }
}
static class TypeMismatch {
private Integer a;
public void setA(Integer a) { this.a = a; }
public String getA() { return this.a.toString(); }
}
static class TypeMismatch2 {
private Integer a;
public void setA(String a) { this.a = Integer.parseInt(a); }
public Integer getA() { return this.a; }
}
// despite having no constructor we should still be able to serialise an instance of C
@Test
public void serialiseC() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
SerializationOutput ser = new SerializationOutput(factory1);
C c1 = new C();
c1.setA(1);
c1.setB(2);
c1.setC(3);
Schema schemas = ser.serializeAndReturnSchema(c1).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C", schemas.component1().get(0).getName());
CompositeType ct = (CompositeType) schemas.component1().get(0);
assertEquals(3, ct.getFields().size());
assertEquals("a", ct.getFields().get(0).getName());
assertEquals("b", ct.getFields().get(1).getName());
assertEquals("c", ct.getFields().get(2).getName());
// No setter for c so should only serialise two properties
C2 c2 = new C2();
c2.setA(1);
c2.setB(2);
schemas = ser.serializeAndReturnSchema(c2).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C2", schemas.component1().get(0).getName());
ct = (CompositeType) schemas.component1().get(0);
// With no setter for c we should only serialise a and b into the stream
assertEquals(2, ct.getFields().size());
assertEquals("a", ct.getFields().get(0).getName());
assertEquals("b", ct.getFields().get(1).getName());
// no getter for b so shouldn't serialise it,thus only a and c should apper in the envelope
C3 c3 = new C3();
c3.setA(1);
c3.setB(2);
c3.setC(3);
schemas = ser.serializeAndReturnSchema(c3).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C3", schemas.component1().get(0).getName());
ct = (CompositeType) schemas.component1().get(0);
// With no setter for c we should only serialise a and b into the stream
assertEquals(2, ct.getFields().size());
assertEquals("a", ct.getFields().get(0).getName());
assertEquals("c", ct.getFields().get(1).getName());
C4 c4 = new C4();
c4.setA(1);
c4.setB(2);
c4.setC(3);
schemas = ser.serializeAndReturnSchema(c4).component2();
assertEquals(1, schemas.component1().size());
assertEquals(this.getClass().getName() + "$C4", schemas.component1().get(0).getName());
ct = (CompositeType) schemas.component1().get(0);
// With non public visibility on a setter and getter for a and b, only c should be serialised
assertEquals(1, ct.getFields().size());
assertEquals("c", ct.getFields().get(0).getName());
}
@Test
public void deserialiseC() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
C cPre1 = new C();
int a = 1;
int b = 2;
int c = 3;
cPre1.setA(a);
cPre1.setB(b);
cPre1.setC(c);
SerializedBytes bytes = new SerializationOutput(factory1).serialize(cPre1);
C cPost1 = new DeserializationInput(factory1).deserialize(bytes, C.class);
assertEquals(a, cPost1.a);
assertEquals(b, cPost1.b);
assertEquals(c, cPost1.c);
C2 cPre2 = new C2();
cPre2.setA(1);
cPre2.setB(2);
C2 cPost2 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre2),
C2.class);
assertEquals(a, cPost2.a);
assertEquals(b, cPost2.b);
// no setter for c means nothing will be serialised and thus it will have the default value of zero
// set
assertEquals(0, cPost2.c);
C3 cPre3 = new C3();
cPre3.setA(1);
cPre3.setB(2);
cPre3.setC(3);
C3 cPost3 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre3),
C3.class);
assertEquals(a, cPost3.a);
// no getter for b means, as before, it'll have been not set and will thus be defaulted to 0
assertEquals(0, cPost3.b);
assertEquals(c, cPost3.c);
C4 cPre4 = new C4();
cPre4.setA(1);
cPre4.setB(2);
cPre4.setC(3);
C4 cPost4 = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(cPre4),
C4.class);
assertEquals(0, cPost4.a);
assertEquals(0, cPost4.b);
assertEquals(c, cPost4.c);
}
@Test
public void serialiseOuterAndInner() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
Inner1 i1 = new Inner1("Hello");
Inner2 i2 = new Inner2();
i2.setA(10.5);
Outer o = new Outer();
o.setA(i1);
o.setB("World");
o.setC(i2);
Outer post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(o),
Outer.class);
assertEquals("Hello", post.a.a);
assertEquals("World", post.b);
assertEquals((Double)10.5, post.c.a);
}
@Test
public void typeMistmatch() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
TypeMismatch tm = new TypeMismatch();
tm.setA(10);
assertEquals("10", tm.getA());
TypeMismatch post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(tm),
TypeMismatch.class);
// because there is a type mismatch in the class, it won't return that info as a BEAN property and thus
// we won't serialise it and thus on deserialization it won't be initialized
Assertions.assertThatThrownBy(() -> post.getA()).isInstanceOf(NullPointerException.class);
}
@Test
public void typeMistmatch2() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
TypeMismatch2 tm = new TypeMismatch2();
tm.setA("10");
assertEquals((Integer)10, tm.getA());
TypeMismatch2 post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(tm),
TypeMismatch2.class);
// because there is a type mismatch in the class, it won't return that info as a BEAN property and thus
// we won't serialise it and thus on deserialization it won't be initialized
assertEquals(null, post.getA());
}
}

View File

@ -30,20 +30,3 @@ class TestSerializationOutput(
fun testName(): String = Thread.currentThread().stackTrace[2].methodName fun testName(): String = Thread.currentThread().stackTrace[2].methodName
data class BytesAndSchemas<T : Any>(
val obj: SerializedBytes<T>,
val schema: Schema,
val transformsSchema: TransformsSchema)
// Extension for the serialize routine that returns the scheme encoded into the
// bytes as well as the bytes for simple testing
@Throws(NotSerializableException::class)
fun <T : Any> SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchemas<T> {
try {
val blob = _serialize(obj)
val schema = Schema(schemaHistory.toList())
return BytesAndSchemas(blob, schema, TransformsSchema.build(schema, serializerFactory))
} finally {
andFinally()
}
}