mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
CORDA-553 - Review Comments
This commit is contained in:
@ -10,10 +10,17 @@ import org.apache.qpid.proton.amqp.UnsignedLong
|
||||
* Repeated here for brevity:
|
||||
* 50530 - R3 - Mike Hearn - mike&r3.com
|
||||
*/
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc562 shl(32 + 16)
|
||||
|
||||
/**
|
||||
* AMQP desriptor ID's for our custom types.
|
||||
*
|
||||
* NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE
|
||||
*
|
||||
* these are encoded as part of a serialised blob and doing so would render us unable to
|
||||
* de-serialise that blob!!!
|
||||
*/
|
||||
enum class AMQPDescriptorRegistry(val id: Long) {
|
||||
|
||||
ENVELOPE(1),
|
||||
SCHEMA(2),
|
||||
OBJECT_DESCRIPTOR(3),
|
||||
|
@ -18,6 +18,14 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.ENVELOPE.amqpDescriptor
|
||||
val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR)
|
||||
|
||||
// described list should either be two or three elements long
|
||||
private const val ENVELOPE_WITHOUT_TRANSFORMS = 2
|
||||
private const val ENVELOPE_WITH_TRANSFORMS = 3
|
||||
|
||||
private const val BLOB_IDX = 0
|
||||
private const val SCHEMA_IDX = 1
|
||||
private const val TRANSFORMS_SCHEMA_IDX = 2
|
||||
|
||||
fun get(data: Data): Envelope {
|
||||
val describedType = data.`object` as DescribedType
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
@ -27,28 +35,29 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
|
||||
|
||||
// We need to cope with objects serialised without the transforms header element in the
|
||||
// envelope
|
||||
val transformSchema : Any? = when (list.size) {
|
||||
2 -> null
|
||||
3 -> list[2]
|
||||
val transformSchema: Any? = when (list.size) {
|
||||
ENVELOPE_WITHOUT_TRANSFORMS -> null
|
||||
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX]
|
||||
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
|
||||
}
|
||||
|
||||
return newInstance(listOf(list[0], Schema.get(list[1]!!), TransformsSchema.newInstance(transformSchema)))
|
||||
return newInstance(listOf(list[BLOB_IDX], Schema.get(list[SCHEMA_IDX]!!),
|
||||
TransformsSchema.newInstance(transformSchema)))
|
||||
}
|
||||
|
||||
// This seperation of functions is needed as this will be the entry point for the default
|
||||
// AMQP decoder if one is used (see the unit tests)
|
||||
// This separation of functions is needed as this will be the entry point for the default
|
||||
// AMQP decoder if one is used (see the unit tests).
|
||||
override fun newInstance(described: Any?): Envelope {
|
||||
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
|
||||
|
||||
// We need to cope with objects serialised without the transforms header element in the
|
||||
// envelope
|
||||
val transformSchema = when (list.size) {
|
||||
2 -> TransformsSchema.newInstance(null)
|
||||
3 -> list[2] as TransformsSchema
|
||||
ENVELOPE_WITHOUT_TRANSFORMS -> TransformsSchema.newInstance(null)
|
||||
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX] as TransformsSchema
|
||||
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
|
||||
}
|
||||
return Envelope(list[0], list[1] as Schema, transformSchema)
|
||||
return Envelope(list[BLOB_IDX], list[SCHEMA_IDX] as Schema, transformSchema)
|
||||
}
|
||||
|
||||
override fun getTypeClass(): Class<*> = Envelope::class.java
|
||||
|
@ -17,11 +17,11 @@ import java.util.*
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
|
||||
|
||||
const val DESCRIPTOR_DOMAIN: String = "net.corda"
|
||||
|
||||
// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB
|
||||
val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray())
|
||||
|
||||
|
||||
/**
|
||||
* This and the classes below are OO representations of the AMQP XML schema described in the specification. Their
|
||||
* [toString] representations generate the associated XML form.
|
||||
|
@ -34,6 +34,8 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
|
||||
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
||||
val classloader: ClassLoader
|
||||
get() = classCarpenter.classloader
|
||||
|
@ -1,19 +1,19 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||
|
||||
/**
|
||||
* Utility class that defines an instance of a transform we support
|
||||
* Utility class that defines an instance of a transform we support.
|
||||
*
|
||||
* @property type The transform annotation
|
||||
* @property type The transform annotation.
|
||||
* @property enum Maps the annotaiton onto a transform type, we expect there are multiple annotations that
|
||||
* would map to a single transform type
|
||||
* @property f Anonymous function that should return a list of Annotations encapsualted by the parent annotation
|
||||
* would map to a single transform type.
|
||||
* @property getAnnotations Anonymous function that should return a list of Annotations encapsualted by the parent annotation
|
||||
* that reference the transform. Notionally this allows the code that extracts transforms to work on single instances
|
||||
* of a transform or a meta list of them
|
||||
* of a transform or a meta list of them.
|
||||
*/
|
||||
data class SupportedTransform(
|
||||
val type: Class<out Annotation>,
|
||||
@ -37,7 +37,7 @@ private val wrapperExtract = { x: Annotation ->
|
||||
private val singleExtract = { x: Annotation -> listOf(x) }
|
||||
|
||||
/**
|
||||
* Utility list of all transforms we support that simplifies our generator
|
||||
* Utility list of all transforms we support that simplifies our generation code
|
||||
*
|
||||
* NOTE: We have to support single instances of the transform annotations as well as the wrapping annotation
|
||||
* when many instances are repeated
|
||||
|
@ -1,10 +1,9 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
import java.io.NotSerializableException
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import java.util.*
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
import java.io.NotSerializableException
|
||||
import java.util.*
|
||||
|
||||
// NOTE: We are effectively going to replicate the annotations, we need to do this because
|
||||
// we can't instantiate instances of those annotation classes and this code needs to
|
||||
@ -14,7 +14,7 @@ import java.io.NotSerializableException
|
||||
* Base class for representations of specific types of transforms as applied to a type within the
|
||||
* Corda serialisation framework
|
||||
*/
|
||||
sealed class Transform : DescribedType {
|
||||
abstract class Transform : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Transform> {
|
||||
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT.amqpDescriptor
|
||||
|
||||
@ -92,10 +92,10 @@ class EnumDefaultSchemeTransform(val old: String, val new: String) : Transform()
|
||||
override fun getDescribed(): Any = listOf(name, old, new)
|
||||
override fun params() = "old=${old.esc()} new=${new.esc()}"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val o = other as? EnumDefaultSchemeTransform ?: return super.equals(other)
|
||||
return o.new == new && o.old == old
|
||||
}
|
||||
override fun equals(other: Any?) = (
|
||||
(other is EnumDefaultSchemeTransform && other.new == new && other.old == old) || super.equals(other))
|
||||
|
||||
override fun hashCode() = (17 * new.hashCode()) + old.hashCode()
|
||||
|
||||
override val name: String get() = typeName
|
||||
}
|
||||
@ -130,17 +130,21 @@ class RenameSchemaTransform(val from: String, val to: String) : Transform() {
|
||||
|
||||
override fun params() = "from=${from.esc()} to=${to.esc()}"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val o = other as? RenameSchemaTransform ?: return super.equals(other)
|
||||
return o.from == from && o.to == to
|
||||
}
|
||||
override fun equals(other: Any?) = (
|
||||
(other is RenameSchemaTransform && other.from == from && other.to == to) || super.equals(other))
|
||||
|
||||
override fun hashCode() = (11 * from.hashCode()) + to.hashCode()
|
||||
|
||||
override val name: String get() = typeName
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @property types is a list of serialised types that have transforms, each list element is a
|
||||
* Represents the set of all transforms that can be a applied to all classes represented as part of
|
||||
* an AMQP schema. It forms a part of the AMQP envelope alongside the [Schema] and the serialized bytes
|
||||
*
|
||||
* @property types maps class names to a map of transformation types. In turn those transformation types
|
||||
* are each a list of instances o that transform.
|
||||
*/
|
||||
data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, MutableList<Transform>>>) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<TransformsSchema> {
|
||||
@ -158,31 +162,38 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
|
||||
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
schema.types.forEach { type ->
|
||||
val clazz = try {
|
||||
sf.classloader.loadClass(type.name)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
return@forEach
|
||||
}
|
||||
sf.transformsCache.computeIfAbsent(type.name) {
|
||||
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
|
||||
try {
|
||||
val clazz = sf.classloader.loadClass(type.name)
|
||||
|
||||
supportedTransforms.forEach { transform ->
|
||||
clazz.getAnnotation(transform.type)?.let { list ->
|
||||
transform.getAnnotations(list).forEach {
|
||||
val t = transform.enum.build(it)
|
||||
supportedTransforms.forEach { transform ->
|
||||
clazz.getAnnotation(transform.type)?.let { list ->
|
||||
transform.getAnnotations(list).forEach { annotation ->
|
||||
val t = transform.enum.build(annotation)
|
||||
|
||||
val m = rtn.computeIfAbsent(type.name) {
|
||||
EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
|
||||
// we're explicitly rejecting repeated annotations, whilst it's fine and we'd just
|
||||
// ignore them it feels like a good thing to alert the user to since this is
|
||||
// more than likely a typo in their code so best make it an actual error
|
||||
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }
|
||||
.filter { t == it }.isNotEmpty()) {
|
||||
throw NotSerializableException(
|
||||
"Repeated unique transformation annotation of type ${t.name}")
|
||||
}
|
||||
|
||||
transforms[transform.enum]!!.add(t)
|
||||
}
|
||||
}
|
||||
|
||||
// we're explicitly rejecting repeated annotations, whilst it's fine and we'd just
|
||||
// ignore them it feels like a good thing to alert the user to since this is
|
||||
// more than likely a typo in their code so best make it an actual error
|
||||
if (m.computeIfAbsent(transform.enum) { mutableListOf() }.filter { it == t }.isNotEmpty()) {
|
||||
throw NotSerializableException(
|
||||
"Repeated unique transformation annotation of type ${t.name}")
|
||||
}
|
||||
|
||||
m[transform.enum]!!.add(t)
|
||||
}
|
||||
} catch (_: ClassNotFoundException) {
|
||||
// if we can't load the class we'll end up caching an empty list which is fine as that
|
||||
// list, on lookup, won't be included in the schema because it's empty
|
||||
}
|
||||
|
||||
transforms
|
||||
}.apply {
|
||||
if (isNotEmpty()) {
|
||||
rtn[type.name] = this
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -198,7 +209,6 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
|
||||
*/
|
||||
override fun newInstance(described: Any?): TransformsSchema {
|
||||
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
val describedType = described as? DescribedType ?: return TransformsSchema(rtn)
|
||||
|
||||
if (describedType.descriptor != DESCRIPTOR) {
|
||||
@ -208,7 +218,6 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
|
||||
val map = describedType.described as? Map<*, *> ?:
|
||||
throw NotSerializableException("Transform schema must be encoded as a map")
|
||||
|
||||
|
||||
map.forEach { type ->
|
||||
val fingerprint = type.key as? String ?:
|
||||
throw NotSerializableException("Fingerprint must be encoded as a string")
|
||||
@ -238,6 +247,7 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
|
||||
override fun toString(): String {
|
||||
data class Indent(val indent: String) {
|
||||
@Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}")
|
||||
|
||||
override fun toString() = indent
|
||||
}
|
||||
|
||||
|
@ -186,6 +186,8 @@ public class JavaSerializationOutputTests {
|
||||
decoder.register(CompositeType.Companion.getDESCRIPTOR(), CompositeType.Companion);
|
||||
decoder.register(Choice.Companion.getDESCRIPTOR(), Choice.Companion);
|
||||
decoder.register(RestrictedType.Companion.getDESCRIPTOR(), RestrictedType.Companion);
|
||||
decoder.register(Transform.Companion.getDESCRIPTOR(), Transform.Companion);
|
||||
decoder.register(TransformsSchema.Companion.getDESCRIPTOR(), TransformsSchema.Companion);
|
||||
|
||||
new EncoderImpl(decoder);
|
||||
decoder.setByteBuffer(ByteBuffer.wrap(bytes.getBytes(), 8, bytes.getSize() - 8));
|
||||
|
@ -377,4 +377,31 @@ class EnumEvolvabilityTests {
|
||||
assertEquals(2, e2S[TransformTypes.EnumDefault]!!.size)
|
||||
assertEquals(1, e3S[TransformTypes.EnumDefault]!!.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCache() {
|
||||
data class C2(val annotatedEnum: AnnotatedEnumOnce)
|
||||
data class C1(val annotatedEnum: AnnotatedEnumOnce)
|
||||
|
||||
val sf = testDefaultFactory()
|
||||
|
||||
assertEquals(0, sf.transformsCache.size)
|
||||
|
||||
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
|
||||
|
||||
assertEquals(2, sf.transformsCache.size)
|
||||
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
|
||||
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
||||
|
||||
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
|
||||
|
||||
assertEquals(3, sf.transformsCache.size)
|
||||
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
|
||||
assertTrue(sf.transformsCache.containsKey(C2::class.java.name))
|
||||
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
||||
|
||||
assertEquals (sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
|
||||
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user