From 988e3e5007ee6d6ae9323f4aa7decbbfabe45624 Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Thu, 14 Sep 2017 09:14:09 +0100 Subject: [PATCH] Some additional serializers to fill in the blanks vs. our default whitelist (#1490) --- .../core/serialization/SerializationAPI.kt | 2 +- .../serialization/AMQPSerializationScheme.kt | 5 + .../serialization/amqp/CustomSerializer.kt | 5 + .../amqp/DeserializedParameterizedType.kt | 2 +- .../serialization/amqp/MapSerializer.kt | 12 +- .../internal/serialization/amqp/Schema.kt | 3 +- .../serialization/amqp/SerializerFactory.kt | 18 +-- .../amqp/custom/BitSetSerializer.kt | 17 +++ .../amqp/custom/EnumSetSerializer.kt | 34 ++++++ .../amqp/custom/InputStreamSerializer.kt | 41 +++++++ .../amqp/custom/SimpleStringSerializer.kt | 9 ++ .../amqp/custom/StringBufferSerializer.kt | 8 ++ .../amqp/custom/ThrowableSerializer.kt | 2 + .../amqp/custom/ZoneIdSerializer.kt | 4 +- .../amqp/SerializationOutputTests.kt | 111 ++++++++++++++++-- 15 files changed, 250 insertions(+), 23 deletions(-) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BitSetSerializer.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/EnumSetSerializer.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/SimpleStringSerializer.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/StringBufferSerializer.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index 7f11505be4..131578482e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -64,7 +64,7 @@ abstract class SerializationFactory { val priorContext = _currentFactory.get() _currentFactory.set(this) try { - return block() + return this.block() } finally { _currentFactory.set(priorContext) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt index bc0b5e1ee0..9888dfc6ab 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt @@ -54,6 +54,11 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) + register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer) + register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer) + register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) + register(net.corda.nodeapi.internal.serialization.amqp.custom.BitSetSerializer(this)) + register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this)) } val customizer = AMQPSerializationCustomization(factory) pluginRegistries.forEach { it.customizeSerialization(customizer) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt index fdb5896444..7188a99b50 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt @@ -29,6 +29,11 @@ abstract class CustomSerializer : AMQPSerializer { */ abstract val schemaForDocumentation: Schema + /** + * Whether subclasses using this serializer via inheritance should have a mapping in the schema. + */ + open val revealSubclassesInSchema: Boolean = false + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { data.withDescribed(descriptor) { @Suppress("UNCHECKED_CAST") diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializedParameterizedType.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializedParameterizedType.kt index 96b2d729d4..729a98ded2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializedParameterizedType.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializedParameterizedType.kt @@ -86,7 +86,7 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p if (pos == typeStart) { skippingWhitespace = false if (params[pos].isWhitespace()) { - typeStart = pos++ + typeStart = ++pos } else if (!needAType) { throw NotSerializableException("Not expecting a type") } else if (params[pos] == '?') { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt index dee9992a5b..0694d4abfa 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt @@ -18,7 +18,7 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *> */ class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer { override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) - override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}") + override val typeDescriptor: Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}") companion object { // NB: Order matters in this map, the most specific classes should be listed at the end @@ -29,7 +29,11 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial NavigableMap::class.java to { map -> Collections.unmodifiableNavigableMap(TreeMap(map)) }, // concrete classes for user convenience LinkedHashMap::class.java to { map -> LinkedHashMap(map) }, - TreeMap::class.java to { map -> TreeMap(map) } + TreeMap::class.java to { map -> TreeMap(map) }, + EnumMap::class.java to { map -> + @Suppress("UNCHECKED_CAST") + EnumMap(map as Map) + } )) private fun findConcreteType(clazz: Class<*>): MapCreationFunction { @@ -95,6 +99,10 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial private fun readEntry(schema: Schema, input: DeserializationInput, entry: Map.Entry) = input.readObjectOrNull(entry.key, schema, declaredType.actualTypeArguments[0]) to input.readObjectOrNull(entry.value, schema, declaredType.actualTypeArguments[1]) + + // Cannot use * as a bound for EnumMap and EnumSet since * is not an enum. So, we use a sample enum instead. + // We don't actually care about the type, we just need to make the compiler happier. + internal enum class EnumJustUsedForCasting { NOT_USED } } internal fun Class<*>.checkSupportedMapType() { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 2e27fbfd9a..b0029d36a5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -468,7 +468,8 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta } } -private fun isCollectionOrMap(type: Class<*>) = Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type) +private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) && + !EnumSet::class.java.isAssignableFrom(type) private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory): Hasher { // Hash the class + properties + interfaces diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index f4bcaa47cc..a32a584d61 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -13,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList import javax.annotation.concurrent.ThreadSafe -data class schemaAndDescriptor(val schema: Schema, val typeDescriptor: Any) +data class FactorySchemaAndDescriptor(val schema: Schema, val typeDescriptor: Any) /** * Factory of serializers designed to be shared across threads and invocations. @@ -22,8 +22,8 @@ data class schemaAndDescriptor(val schema: Schema, val typeDescriptor: Any) // TODO: maybe support for caching of serialized form of some core types for performance // TODO: profile for performance in general // TODO: use guava caches etc so not unbounded -// TODO: do we need to support a transient annotation to exclude certain properties? // TODO: allow definition of well known types that are left out of the schema. +// TODO: migrate some core types to unsigned integer descriptor // TODO: document and alert to the fact that classes cannot default superclass/interface properties otherwise they are "erased" due to matching with constructor. // TODO: type name prefixes for interfaces and abstract classes? Or use label? // TODO: generic types should define restricted type alias with source of the wildcarded version, I think, if we're to generate classes from schema @@ -64,7 +64,8 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { // 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))) -> { + (actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) && + !EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> { val declaredTypeAmended= CollectionSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass) serializersByType.computeIfAbsent(declaredTypeAmended) { CollectionSerializer(declaredTypeAmended, this) @@ -79,7 +80,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { makeMapSerializer(declaredTypeAmended) } } - Enum::class.java.isAssignableFrom(declaredClass) -> serializersByType.computeIfAbsent(declaredClass) { + Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> serializersByType.computeIfAbsent(actualClass ?: declaredClass) { EnumSerializer(actualType, actualClass ?: declaredClass, this) } else -> makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType) @@ -164,7 +165,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { @Throws(NotSerializableException::class) fun get(typeDescriptor: Any, schema: Schema): AMQPSerializer { return serializersByDescriptor[typeDescriptor] ?: { - processSchema(schemaAndDescriptor(schema, typeDescriptor)) + processSchema(FactorySchemaAndDescriptor(schema, typeDescriptor)) serializersByDescriptor[typeDescriptor] ?: throw NotSerializableException( "Could not find type matching descriptor $typeDescriptor.") }() @@ -188,7 +189,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { * Iterate over an AMQP schema, for each type ascertain weather it's on ClassPath of [classloader] amd * if not use the [ClassCarpenter] to generate a class to use in it's place */ - private fun processSchema(schema: schemaAndDescriptor, sentinel: Boolean = false) { + private fun processSchema(schema: FactorySchemaAndDescriptor, sentinel: Boolean = false) { val carpenterSchemas = CarpenterSchemas.newInstance() for (typeNotation in schema.schema.types) { try { @@ -234,8 +235,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { } else { findCustomSerializer(clazz, declaredType) ?: run { if (type.isArray()) { - // Allow Object[] since this can be quite common (i.e. an untyped array) - if (type.componentType() != Object::class.java) whitelisted(type.componentType()) + // Don't need to check the whitelist since each element will come back through the whitelisting process. if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this) else ArraySerializer.make(type, this) } else if (clazz.kotlin.objectInstance != null) { @@ -256,7 +256,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { for (customSerializer in customSerializers) { if (customSerializer.isSerializerFor(clazz)) { val declaredSuperClass = declaredType.asClass()?.superclass - if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass)) { + if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass) || !customSerializer.revealSubclassesInSchema) { return customSerializer } else { // Make a subclass serializer for the subclass and return that... diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BitSetSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BitSetSerializer.kt new file mode 100644 index 0000000000..cf62d48d5c --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BitSetSerializer.kt @@ -0,0 +1,17 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import java.util.* + +/** + * A serializer that writes out a [BitSet] as an integer number of bits, plus the necessary number of bytes to encode that + * many bits. + */ +class BitSetSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(BitSet::class.java, BitSetProxy::class.java, factory) { + override fun toProxy(obj: BitSet): BitSetProxy = BitSetProxy(obj.toByteArray()) + + override fun fromProxy(proxy: BitSetProxy): BitSet = BitSet.valueOf(proxy.bytes) + + data class BitSetProxy(val bytes: ByteArray) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/EnumSetSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/EnumSetSerializer.kt new file mode 100644 index 0000000000..29527d21dd --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/EnumSetSerializer.kt @@ -0,0 +1,34 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.MapSerializer +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import java.util.* + +@Suppress("UNCHECKED_CAST") +/** + * A serializer that writes out an [EnumSet] as a type, plus list of instances in the set. + */ +class EnumSetSerializer(factory: SerializerFactory) : CustomSerializer.Proxy, EnumSetSerializer.EnumSetProxy>(EnumSet::class.java, EnumSetProxy::class.java, factory) { + override val additionalSerializers: Iterable> = listOf(ClassSerializer(factory)) + + override fun toProxy(obj: EnumSet<*>): EnumSetProxy = EnumSetProxy(elementType(obj), obj.toList()) + + private fun elementType(set: EnumSet<*>): Class<*> { + return if (set.isEmpty()) { + EnumSet.complementOf(set as EnumSet).first().javaClass + } else { + set.first().javaClass + } + } + + override fun fromProxy(proxy: EnumSetProxy): EnumSet<*> { + return if (proxy.elements.isEmpty()) { + EnumSet.noneOf(proxy.clazz as Class) + } else { + EnumSet.copyOf(proxy.elements as List) + } + } + + data class EnumSetProxy(val clazz: Class<*>, val elements: List) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt new file mode 100644 index 0000000000..772ab65771 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt @@ -0,0 +1,41 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.* +import org.apache.qpid.proton.amqp.Binary +import org.apache.qpid.proton.codec.Data +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.lang.reflect.Type + +/** + * A serializer that writes out the content of an input stream as bytes and deserializes into a [ByteArrayInputStream]. + */ +object InputStreamSerializer : CustomSerializer.Implements(InputStream::class.java) { + override val revealSubclassesInSchema: Boolean = true + + override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) + + override fun writeDescribedObject(obj: InputStream, data: Data, type: Type, output: SerializationOutput) { + val startingSize = maxOf(4096, obj.available() + 1) + var buffer = ByteArray(startingSize) + var pos = 0 + while (true) { + val numberOfBytesRead = obj.read(buffer, pos, buffer.size - pos) + if (numberOfBytesRead != -1) { + pos += numberOfBytesRead + // If the buffer is now full, resize it. + if (pos == buffer.size) { + buffer = buffer.copyOf(buffer.size + maxOf(4096, obj.available() + 1)) + } + } else { + data.putBinary(Binary(buffer, 0, pos)) + break + } + } + } + + override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): InputStream { + val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray + return ByteArrayInputStream(bits) + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/SimpleStringSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/SimpleStringSerializer.kt new file mode 100644 index 0000000000..0243558332 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/SimpleStringSerializer.kt @@ -0,0 +1,9 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer +import org.apache.activemq.artemis.api.core.SimpleString + +/** + * A serializer for [SimpleString]. + */ +object SimpleStringSerializer : CustomSerializer.ToString(SimpleString::class.java) \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/StringBufferSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/StringBufferSerializer.kt new file mode 100644 index 0000000000..2c738b4d76 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/StringBufferSerializer.kt @@ -0,0 +1,8 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer + +/** + * A serializer for [StringBuffer]. + */ +object StringBufferSerializer : CustomSerializer.ToString(StringBuffer::class.java) \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt index 6600982658..696b2616b9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt @@ -13,6 +13,8 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy() } + override val revealSubclassesInSchema: Boolean = true + override val additionalSerializers: Iterable> = listOf(StackTraceElementSerializer(factory)) override fun toProxy(obj: Throwable): ThrowableProxy { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZoneIdSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZoneIdSerializer.kt index 87ab278dda..1d590a61da 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZoneIdSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ZoneIdSerializer.kt @@ -2,12 +2,14 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import java.time.* +import java.time.ZoneId /** * A serializer for [ZoneId] that uses a proxy object to write out the string form. */ class ZoneIdSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(ZoneId::class.java, ZoneIdProxy::class.java, factory) { + override val revealSubclassesInSchema: Boolean = true + override fun toProxy(obj: ZoneId): ZoneIdProxy = ZoneIdProxy(obj.id) override fun fromProxy(proxy: ZoneIdProxy): ZoneId = ZoneId.of(proxy.id) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 5121b3124d..7c18af1465 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -20,6 +20,7 @@ import net.corda.testing.BOB_IDENTITY import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_PUBKEY import net.corda.testing.withTestSerialization +import org.apache.activemq.artemis.api.core.SimpleString import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl @@ -27,6 +28,7 @@ import org.junit.Assert.assertNotSame import org.junit.Assert.assertSame import org.junit.Ignore import org.junit.Test +import java.io.ByteArrayInputStream import java.io.IOException import java.io.NotSerializableException import java.math.BigDecimal @@ -180,7 +182,7 @@ class SerializationOutputTests { assertTrue(Objects.deepEquals(desObj, desObj2) == expectDeserializedEqual) // TODO: add some schema assertions to check correctly formed. - return desObj2 + return desObj } @Test @@ -355,7 +357,7 @@ class SerializationOutputTests { serdes(obj) } - @Test(expected = NotSerializableException::class) + @Test fun `test TreeMap`() { val obj = TreeMap() obj[456] = Foo("Fred", 123) @@ -720,8 +722,18 @@ class SerializationOutputTests { serdes(obj, factory, factory2) } - // TODO: ignored due to Proton-J bug https://issues.apache.org/jira/browse/PROTON-1551 - @Ignore + @Test + fun `test month serialize`() { + val obj = Month.APRIL + serdes(obj) + } + + @Test + fun `test day of week serialize`() { + val obj = DayOfWeek.FRIDAY + serdes(obj) + } + @Test fun `test certificate holder serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -734,8 +746,6 @@ class SerializationOutputTests { serdes(obj, factory, factory2) } - // TODO: ignored due to Proton-J bug https://issues.apache.org/jira/browse/PROTON-1551 - @Ignore @Test fun `test party and certificate serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -806,7 +816,6 @@ class SerializationOutputTests { data class Bob(val byteArrays: List) - @Ignore("Causes DeserializedParameterizedType.make() to fail") @Test fun `test list of byte arrays`() { val a = ByteArray(1) @@ -815,7 +824,9 @@ class SerializationOutputTests { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - serdes(obj, factory, factory2) + val obj2 = serdes(obj, factory, factory2, false, false) + + assertNotSame(obj2.byteArrays[0], obj2.byteArrays[2]) } data class Vic(val a: List, val b: List) @@ -874,4 +885,88 @@ class SerializationOutputTests { val objCopy = serdes(obj, factory, factory2, false, false) assertNotSame(objCopy.a, objCopy.b) } + + @Test + fun `test StringBuffer serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer) + + val obj = StringBuffer("Bob") + val obj2 = serdes(obj, factory, factory2, false, false) + assertEquals(obj.toString(), obj2.toString()) + } + + @Test + fun `test SimpleString serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer) + + val obj = SimpleString("Bob") + serdes(obj, factory, factory2) + } + + @Test + fun `test kotlin Unit serialize`() { + val obj = Unit + serdes(obj) + } + + @Test + fun `test kotlin Pair serialize`() { + val obj = Pair("a", 3) + serdes(obj) + } + + @Test + fun `test InputStream serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) + val bytes = ByteArray(10) { it.toByte() } + val obj = ByteArrayInputStream(bytes) + val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) + val obj3 = ByteArrayInputStream(bytes) // Can't use original since the stream pointer has moved. + assertEquals(obj3.available(), obj2.available()) + assertEquals(obj3.read(), obj2.read()) + } + + @Test + fun `test EnumSet serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(factory)) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(factory2)) + + val obj = EnumSet.of(Month.APRIL, Month.AUGUST) + serdes(obj, factory, factory2) + } + + @Test + fun `test BitSet serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.BitSetSerializer(factory)) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.BitSetSerializer(factory2)) + + val obj = BitSet.valueOf(kotlin.ByteArray(16) { it.toByte() }).get(0, 123) + serdes(obj, factory, factory2) + } + + @Test + fun `test EnumMap serialize`() { + val obj = EnumMap(Month::class.java) + obj[Month.APRIL] = Month.APRIL.value + obj[Month.AUGUST] = Month.AUGUST.value + serdes(obj) + } } \ No newline at end of file