CORDA-540: Allow lists as root object in AMQP serialization graph

This commit is contained in:
Viktor Kolomeyko 2017-09-05 13:33:00 +01:00 committed by Viktor Kolomeyko
parent ebc9cacb53
commit cd912f8add
3 changed files with 52 additions and 8 deletions

View File

@ -18,18 +18,42 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
companion object {
private val supportedTypes: Map<Class<out Collection<*>>, (List<*>) -> Collection<*>> = mapOf(
// NB: Order matters in this map, the most specific classes should be listed at the end
private val supportedTypes: Map<Class<out Collection<*>>, (List<*>) -> Collection<*>> = Collections.unmodifiableMap(linkedMapOf(
Collection::class.java to { list -> Collections.unmodifiableCollection(list) },
List::class.java to { list -> Collections.unmodifiableList(list) },
Set::class.java to { list -> Collections.unmodifiableSet(LinkedHashSet(list)) },
SortedSet::class.java to { list -> Collections.unmodifiableSortedSet(TreeSet(list)) },
NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) },
NonEmptySet::class.java to { list -> NonEmptySet.copyOf(list) }
)
))
private fun findConcreteType(clazz: Class<*>): (List<*>) -> Collection<*> {
return supportedTypes[clazz] ?: throw NotSerializableException("Unsupported collection type $clazz.")
}
fun deriveParameterizedType(declaredType: Type, declaredClass: Class<*>, actualClass: Class<*>?): ParameterizedType {
if(supportedTypes.containsKey(declaredClass)) {
// Simple case - it is already known to be a collection.
@Suppress("UNCHECKED_CAST")
return deriveParametrizedType(declaredType, declaredClass as Class<out Collection<*>>)
}
else if (actualClass != null && Collection::class.java.isAssignableFrom(actualClass)) {
// Declared class is not collection, but [actualClass] is - represent it accordingly.
val collectionClass = findMostSuitableCollectionType(actualClass)
return deriveParametrizedType(declaredType, collectionClass)
}
throw NotSerializableException("Cannot derive collection type for declaredType: '$declaredType', declaredClass: '$declaredClass', actualClass: '$actualClass'")
}
private fun deriveParametrizedType(declaredType: Type, collectionClass: Class<out Collection<*>>): ParameterizedType =
(declaredType as? ParameterizedType) ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType))
private fun findMostSuitableCollectionType(actualClass: Class<*>): Class<out Collection<*>> =
supportedTypes.keys.findLast { it.isAssignableFrom(actualClass) }!!
}
private val concreteBuilder: (List<*>) -> Collection<*> = findConcreteType(declaredType.rawType as Class<*>)

View File

@ -4,6 +4,7 @@ import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeResolver
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.amqp.CollectionSerializer.Companion.deriveParameterizedType
import net.corda.nodeapi.internal.serialization.carpenter.*
import org.apache.qpid.proton.amqp.*
import java.io.NotSerializableException
@ -69,10 +70,15 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
val serializer = when {
(Collection::class.java.isAssignableFrom(declaredClass)) -> {
serializersByType.computeIfAbsent(declaredType) {
CollectionSerializer(declaredType as? ParameterizedType ?: DeserializedParameterizedType(
declaredClass, arrayOf(AnyType), null), this)
(Collection::class.java.isAssignableFrom(declaredClass) ||
// declared class may not be set to Collection, but actual class could be a collection.
// In this case use of CollectionSerializer is perfectly appropriate.
(actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) -> {
val declaredTypeAmended= deriveParameterizedType(declaredType, declaredClass, actualClass)
serializersByType.computeIfAbsent(declaredTypeAmended) {
CollectionSerializer(declaredTypeAmended, this)
}
}
Map::class.java.isAssignableFrom(declaredClass) -> serializersByType.computeIfAbsent(declaredClass) {

View File

@ -3,19 +3,33 @@ package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.kryoSpecific
import org.junit.Test
import kotlin.test.assertEquals
class ListsSerializationTest : TestDependencyInjectionBase() {
@Test
fun `check list can be serialized as root of serialization graph`() = kryoSpecific<ListsSerializationTest>("AMQP doesn't support lists as the root of serialization graph"){
fun `check list can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(listOf(1))
assertEqualAfterRoundTripSerialization(listOf(1, 2))
}
@Test
fun `check list can be serialized as part of SessionData`() {
run {
val sessionData = SessionData(123, listOf(1))
assertEqualAfterRoundTripSerialization(sessionData)
}
run {
val sessionData = SessionData(123, listOf(1, 2))
assertEqualAfterRoundTripSerialization(sessionData)
}
}
private inline fun<reified T : Any> assertEqualAfterRoundTripSerialization(obj: T) {
val serializedForm: SerializedBytes<T> = obj.serialize()