mirror of
https://github.com/corda/corda.git
synced 2025-02-01 16:58:27 +00:00
CORDA-553 - Review comments
This commit is contained in:
parent
541207738a
commit
32ebd2cc8b
@ -6,10 +6,11 @@ Here are release notes for each snapshot release from M9 onwards.
|
|||||||
Unreleased
|
Unreleased
|
||||||
----------
|
----------
|
||||||
|
|
||||||
** Enum Class Evolution
|
* **Enum Class Evolution**
|
||||||
With the addition of AMQP serialization Corda now supports enum constant evolution, that is the abilit to alter
|
With the addition of AMQP serialization Corda now supports enum constant evolution.
|
||||||
an enum constant and, as long as certain rules ar followed and the correct annotations applied, have older and
|
|
||||||
newer instances of that enumeration be understood.
|
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
|
||||||
|
annotations applied, have older and newer instances of that enumeration be understood.
|
||||||
|
|
||||||
Release 2.0
|
Release 2.0
|
||||||
----------
|
----------
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
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
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
|
import java.lang.UnsupportedOperationException
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property clazz The enum as it exists now, not as it did when it was seialised (either in the past
|
* @property clazz The enum as it exists now, not as it did when it was serialized (either in the past
|
||||||
* or future).
|
* or future).
|
||||||
* @property factory the [SerializerFactory] that is building this serialization object.
|
* @property factory the [SerializerFactory] that is building this serialization object.
|
||||||
* @property conversions A mapping between all potential enum constants that could've been assigned as
|
* @property conversions A mapping between all potential enum constants that could've been assigned to
|
||||||
* existed at serialisation and those that exist now
|
* an instance of the enum as it existed at serialisation and those that exist now
|
||||||
* @property ordinals Convenience mapping of constant to ordinality
|
* @property ordinals Convenience mapping of constant to ordinality
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
class EnumEvolutionSerializer(
|
class EnumEvolutionSerializer(
|
||||||
clazz: Type,
|
clazz: Type,
|
||||||
factory: SerializerFactory,
|
factory: SerializerFactory,
|
||||||
private val conversions : Map<String, String>,
|
private val conversions: Map<String, String>,
|
||||||
private val ordinals : Map<String, Int>) : AMQPSerializer<Any> {
|
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
|
||||||
override val type: Type = clazz
|
override val type: Type = clazz
|
||||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")!!
|
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")!!
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun MutableMap<String, String>.mapInPlace(f : (String)->String) {
|
private fun MutableMap<String, String>.mapInPlace(f: (String) -> String) {
|
||||||
val i = this.iterator()
|
val i = iterator()
|
||||||
while(i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
val curr = (i.next())
|
val curr = i.next()
|
||||||
curr.setValue(f(curr.value))
|
curr.setValue(f(curr.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ class EnumEvolutionSerializer(
|
|||||||
/**
|
/**
|
||||||
* Builds an Enum Evolver serializer.
|
* Builds an Enum Evolver serializer.
|
||||||
*
|
*
|
||||||
* @param old The description of the enum as it existed at the time of serialisation take from the
|
* @param old The description of the enum as it existed at the time of serialisation taken from the
|
||||||
* received AMQP header
|
* received AMQP header
|
||||||
* @param new The Serializer object we built based on the current state of the enum class on our classpath
|
* @param new The Serializer object we built based on the current state of the enum class on our classpath
|
||||||
* @param factory the [SerializerFactory] that is building this serialization object.
|
* @param factory the [SerializerFactory] that is building this serialization object.
|
||||||
@ -53,21 +53,19 @@ class EnumEvolutionSerializer(
|
|||||||
|
|
||||||
// if either of these isn't of the cast type then something has gone terribly wrong
|
// if either of these isn't of the cast type then something has gone terribly wrong
|
||||||
// elsewhere in the code
|
// elsewhere in the code
|
||||||
@Suppress("UNCHECKED_CAST")
|
val defaultRules: List<EnumDefaultSchemaTransform>? = uncheckedCast(transforms[TransformTypes.EnumDefault])
|
||||||
val defaultRules = transforms[TransformTypes.EnumDefault] as? List<EnumDefaultSchemaTransform>
|
val renameRules: List<RenameSchemaTransform>? = uncheckedCast(transforms[TransformTypes.Rename])
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val renameRules = transforms[TransformTypes.Rename] as? List<RenameSchemaTransform>
|
|
||||||
|
|
||||||
// What values exist on the enum as it exists on the class path
|
// What values exist on the enum as it exists on the class path
|
||||||
val localValues = new.type.asClass()!!.enumConstants.map { it.toString() }
|
val localValues = new.type.asClass()!!.enumConstants.map { it.toString() }
|
||||||
|
|
||||||
val conversions : MutableMap<String, String> = new.type.asClass()!!.enumConstants.map { it.toString() }
|
val conversions: MutableMap<String, String> = localValues
|
||||||
.union(defaultRules?.map { it.new }?.toSet() ?: emptySet())
|
.union(defaultRules?.map { it.new }?.toSet() ?: emptySet())
|
||||||
.union(renameRules?.map { it.to } ?: emptySet())
|
.union(renameRules?.map { it.to } ?: emptySet())
|
||||||
.associateBy({ it }, { it })
|
.associateBy({ it }, { it })
|
||||||
.toMutableMap()
|
.toMutableMap()
|
||||||
|
|
||||||
val rules : MutableMap<String, String> = mutableMapOf()
|
val rules: MutableMap<String, String> = mutableMapOf()
|
||||||
rules.putAll(defaultRules?.associateBy({ it.new }, { it.old }) ?: emptyMap())
|
rules.putAll(defaultRules?.associateBy({ it.new }, { it.old }) ?: emptyMap())
|
||||||
rules.putAll(renameRules?.associateBy({ it.to }, { it.from }) ?: emptyMap())
|
rules.putAll(renameRules?.associateBy({ it.to }, { it.from }) ?: emptyMap())
|
||||||
|
|
||||||
@ -79,7 +77,7 @@ class EnumEvolutionSerializer(
|
|||||||
// rather tricky when you don't have access to the actual type, so this is a nice way to be able
|
// rather tricky when you don't have access to the actual type, so this is a nice way to be able
|
||||||
// to precompute and pass to the actual object
|
// to precompute and pass to the actual object
|
||||||
var idx = 0
|
var idx = 0
|
||||||
return EnumEvolutionSerializer(new.type, factory, conversions, localValues.associateBy( {it}, { idx++ }))
|
return EnumEvolutionSerializer(new.type, factory, conversions, localValues.associateBy({ it }, { idx++ }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,17 +85,17 @@ class EnumEvolutionSerializer(
|
|||||||
val enumName = (obj as List<*>)[0] as String
|
val enumName = (obj as List<*>)[0] as String
|
||||||
|
|
||||||
if (enumName !in conversions) {
|
if (enumName !in conversions) {
|
||||||
throw NotSerializableException ("No rule to evolve enum constant $type::$enumName")
|
throw NotSerializableException("No rule to evolve enum constant $type::$enumName")
|
||||||
}
|
}
|
||||||
|
|
||||||
return type.asClass()!!.enumConstants[ordinals[conversions[enumName]]!!]
|
return type.asClass()!!.enumConstants[ordinals[conversions[enumName]]!!]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeClassInfo(output: SerializationOutput) {
|
override fun writeClassInfo(output: SerializationOutput) {
|
||||||
throw IllegalAccessException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||||
throw IllegalAccessException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -109,7 +109,7 @@ class EvolutionSerializer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||||
throw IllegalAccessException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||||
@ -43,7 +44,7 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType
|
|||||||
* @param constants The list of enum constants on the type the transforms are being applied to
|
* @param constants The list of enum constants on the type the transforms are being applied to
|
||||||
*/
|
*/
|
||||||
override fun validate(l : List<Transform>, constants: Set<String>) {
|
override fun validate(l : List<Transform>, constants: Set<String>) {
|
||||||
@Suppress("UNCHECKED_CAST") (l as List<EnumDefaultSchemaTransform>).forEach {
|
uncheckedCast<List<Transform>, List<EnumDefaultSchemaTransform>>(l).forEach {
|
||||||
if (!constants.contains(it.old)) {
|
if (!constants.contains(it.old)) {
|
||||||
throw NotSerializableException(
|
throw NotSerializableException(
|
||||||
"Enum extension defaults must be to a valid constant: ${it.new} -> ${it.old}. ${it.old} " +
|
"Enum extension defaults must be to a valid constant: ${it.new} -> ${it.old}. ${it.old} " +
|
||||||
|
@ -146,10 +146,8 @@ class EnumDefaultSchemaTransform(val old: String, val new: String) : Transform()
|
|||||||
/**
|
/**
|
||||||
* Transform applied to either a class or enum where a property is renamed
|
* Transform applied to either a class or enum where a property is renamed
|
||||||
*
|
*
|
||||||
* @property from the name at time of change of the property
|
* @property from the name of the property or constant prior to being changed, i.e. what it was
|
||||||
* @property to the new name of the property
|
* @property to the new name of the property or constant after the change has been made, i.e. what it is now
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
class RenameSchemaTransform(val from: String, val to: String) : Transform() {
|
class RenameSchemaTransform(val from: String, val to: String) : Transform() {
|
||||||
companion object : DescribedTypeConstructor<RenameSchemaTransform> {
|
companion object : DescribedTypeConstructor<RenameSchemaTransform> {
|
||||||
|
@ -27,7 +27,7 @@ class EnumEvolveTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun deserialiseNewerSetToUnknown() {
|
fun deserialiseNewerSetToUnknown() {
|
||||||
val resource = "${this.javaClass.simpleName}.${testName()}"
|
val resource = "${javaClass.simpleName}.${testName()}"
|
||||||
val sf = testDefaultFactory()
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
data class C (val e : DeserializeNewerSetToUnknown)
|
data class C (val e : DeserializeNewerSetToUnknown)
|
||||||
@ -55,7 +55,7 @@ class EnumEvolveTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun deserialiseNewerSetToUnknown2() {
|
fun deserialiseNewerSetToUnknown2() {
|
||||||
val resource = "${this.javaClass.simpleName}.${testName()}"
|
val resource = "${javaClass.simpleName}.${testName()}"
|
||||||
val sf = testDefaultFactory()
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
data class C(val e: DeserializeNewerSetToUnknown2)
|
data class C(val e: DeserializeNewerSetToUnknown2)
|
||||||
@ -95,7 +95,7 @@ class EnumEvolveTests {
|
|||||||
// Lets test to see if they forgot to provide an upgrade rule
|
// Lets test to see if they forgot to provide an upgrade rule
|
||||||
@Test
|
@Test
|
||||||
fun deserialiseNewerWithNoRule() {
|
fun deserialiseNewerWithNoRule() {
|
||||||
val resource = "${this.javaClass.simpleName}.${testName()}"
|
val resource = "${javaClass.simpleName}.${testName()}"
|
||||||
val sf = testDefaultFactory()
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
data class C(val e: DeserializeNewerWithNoRule)
|
data class C(val e: DeserializeNewerWithNoRule)
|
||||||
@ -143,7 +143,7 @@ class EnumEvolveTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun deserializeWithRename() {
|
fun deserializeWithRename() {
|
||||||
val resource = "${this.javaClass.simpleName}.${testName()}"
|
val resource = "${javaClass.simpleName}.${testName()}"
|
||||||
val sf = testDefaultFactory()
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
data class C(val e: DeserializeWithRename)
|
data class C(val e: DeserializeWithRename)
|
||||||
@ -265,7 +265,7 @@ class EnumEvolveTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun multiOperations() {
|
fun multiOperations() {
|
||||||
val resource = "${this.javaClass.simpleName}.${testName()}"
|
val resource = "${javaClass.simpleName}.${testName()}"
|
||||||
val sf = testDefaultFactory()
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
data class C(val e: MultiOperations)
|
data class C(val e: MultiOperations)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user