mirror of
https://github.com/corda/corda.git
synced 2025-06-17 22:58:19 +00:00
CORDA-540: Allow lists as root object in AMQP serialization graph
This commit is contained in:
committed by
Viktor Kolomeyko
parent
ebc9cacb53
commit
cd912f8add
@ -18,18 +18,42 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
|
|||||||
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
|
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
|
||||||
|
|
||||||
companion object {
|
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) },
|
Collection::class.java to { list -> Collections.unmodifiableCollection(list) },
|
||||||
List::class.java to { list -> Collections.unmodifiableList(list) },
|
List::class.java to { list -> Collections.unmodifiableList(list) },
|
||||||
Set::class.java to { list -> Collections.unmodifiableSet(LinkedHashSet(list)) },
|
Set::class.java to { list -> Collections.unmodifiableSet(LinkedHashSet(list)) },
|
||||||
SortedSet::class.java to { list -> Collections.unmodifiableSortedSet(TreeSet(list)) },
|
SortedSet::class.java to { list -> Collections.unmodifiableSortedSet(TreeSet(list)) },
|
||||||
NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) },
|
NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) },
|
||||||
NonEmptySet::class.java to { list -> NonEmptySet.copyOf(list) }
|
NonEmptySet::class.java to { list -> NonEmptySet.copyOf(list) }
|
||||||
)
|
))
|
||||||
|
|
||||||
private fun findConcreteType(clazz: Class<*>): (List<*>) -> Collection<*> {
|
private fun findConcreteType(clazz: Class<*>): (List<*>) -> Collection<*> {
|
||||||
return supportedTypes[clazz] ?: throw NotSerializableException("Unsupported collection type $clazz.")
|
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<*>)
|
private val concreteBuilder: (List<*>) -> Collection<*> = findConcreteType(declaredType.rawType as Class<*>)
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.primitives.Primitives
|
|||||||
import com.google.common.reflect.TypeResolver
|
import com.google.common.reflect.TypeResolver
|
||||||
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.nodeapi.internal.serialization.amqp.CollectionSerializer.Companion.deriveParameterizedType
|
||||||
import net.corda.nodeapi.internal.serialization.carpenter.*
|
import net.corda.nodeapi.internal.serialization.carpenter.*
|
||||||
import org.apache.qpid.proton.amqp.*
|
import org.apache.qpid.proton.amqp.*
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
@ -69,10 +70,15 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||||
|
|
||||||
val serializer = when {
|
val serializer = when {
|
||||||
(Collection::class.java.isAssignableFrom(declaredClass)) -> {
|
(Collection::class.java.isAssignableFrom(declaredClass) ||
|
||||||
serializersByType.computeIfAbsent(declaredType) {
|
// declared class may not be set to Collection, but actual class could be a collection.
|
||||||
CollectionSerializer(declaredType as? ParameterizedType ?: DeserializedParameterizedType(
|
// In this case use of CollectionSerializer is perfectly appropriate.
|
||||||
declaredClass, arrayOf(AnyType), null), this)
|
(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) {
|
Map::class.java.isAssignableFrom(declaredClass) -> serializersByType.computeIfAbsent(declaredClass) {
|
||||||
|
@ -3,19 +3,33 @@ package net.corda.nodeapi.internal.serialization
|
|||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.node.services.statemachine.SessionData
|
||||||
import net.corda.testing.TestDependencyInjectionBase
|
import net.corda.testing.TestDependencyInjectionBase
|
||||||
import net.corda.testing.kryoSpecific
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class ListsSerializationTest : TestDependencyInjectionBase() {
|
class ListsSerializationTest : TestDependencyInjectionBase() {
|
||||||
|
|
||||||
@Test
|
@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))
|
||||||
assertEqualAfterRoundTripSerialization(listOf(1, 2))
|
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) {
|
private inline fun<reified T : Any> assertEqualAfterRoundTripSerialization(obj: T) {
|
||||||
|
|
||||||
val serializedForm: SerializedBytes<T> = obj.serialize()
|
val serializedForm: SerializedBytes<T> = obj.serialize()
|
||||||
|
Reference in New Issue
Block a user