CORDA-1238: Updated JacksonSupport to serialise pojos annotated with @CordaSerializable with the same properties as the AMQP serialisation framework. (#3162)

This fixes an issue for pojos with getters that aren't c'tor-based properties. Jackson serialises these out but is then unable to deserialise the pojo back.
This commit is contained in:
Shams Asari 2018-05-17 18:34:12 +01:00 committed by GitHub
parent 3cdd908714
commit 824adca6c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 277 additions and 205 deletions

View File

@ -5,7 +5,7 @@ apply plugin: 'net.corda.plugins.api-scanner'
apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':core')
compile project(':serialization')
testCompile project(':test-utils')
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

View File

@ -1,16 +1,17 @@
package net.corda.client.jackson
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.convertValue
import net.corda.client.jackson.internal.CordaModule
import net.corda.client.jackson.internal.ToStringSerialize
import net.corda.client.jackson.internal.jsonObject
import net.corda.client.jackson.internal.readValueAs
import net.corda.core.CordaInternal
@ -20,23 +21,24 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.CertRole
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.core.utilities.toBase58String
import org.bouncycastle.asn1.x509.KeyPurposeId
import java.lang.reflect.Modifier
import java.math.BigDecimal
@ -93,31 +95,9 @@ object JacksonSupport {
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null
}
val cordaModule: Module by lazy {
SimpleModule("core").apply {
setMixInAnnotation(BigDecimal::class.java, BigDecimalMixin::class.java)
setMixInAnnotation(X500Principal::class.java, X500PrincipalMixin::class.java)
setMixInAnnotation(X509Certificate::class.java, X509CertificateMixin::class.java)
setMixInAnnotation(PartyAndCertificate::class.java, PartyAndCertificateSerializerMixin::class.java)
setMixInAnnotation(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java)
setMixInAnnotation(CordaX500Name::class.java, CordaX500NameMixin::class.java)
setMixInAnnotation(Amount::class.java, AmountMixin::class.java)
setMixInAnnotation(AbstractParty::class.java, AbstractPartyMixin::class.java)
setMixInAnnotation(AnonymousParty::class.java, AnonymousPartyMixin::class.java)
setMixInAnnotation(Party::class.java, PartyMixin::class.java)
setMixInAnnotation(PublicKey::class.java, PublicKeyMixin::class.java)
setMixInAnnotation(ByteSequence::class.java, ByteSequenceMixin::class.java)
setMixInAnnotation(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
setMixInAnnotation(SerializedBytes::class.java, SerializedBytesMixin::class.java)
setMixInAnnotation(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
setMixInAnnotation(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
setMixInAnnotation(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
setMixInAnnotation(SignedTransaction::class.java, SignedTransactionMixin2::class.java)
setMixInAnnotation(WireTransaction::class.java, WireTransactionMixin::class.java)
setMixInAnnotation(CertPath::class.java, CertPathMixin::class.java)
setMixInAnnotation(NodeInfo::class.java, NodeInfoMixin::class.java)
}
}
@Suppress("unused")
@Deprecated("Do not use this as it's not thread safe. Instead get a ObjectMapper instance with one of the create*Mapper methods.")
val cordaModule: Module by lazy(::CordaModule)
/**
* Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names.
@ -172,15 +152,16 @@ object JacksonSupport {
registerModule(JavaTimeModule().apply {
addSerializer(Date::class.java, DateSerializer)
})
registerModule(cordaModule)
registerModule(CordaModule())
registerModule(KotlinModule())
addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java)
addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java)
addMixIn(X509Certificate::class.java, X509CertificateMixin::class.java)
addMixIn(CertPath::class.java, CertPathMixin::class.java)
}
}
@JacksonAnnotationsInside
@JsonSerialize(using = com.fasterxml.jackson.databind.ser.std.ToStringSerializer::class)
private annotation class ToStringSerialize
@ToStringSerialize
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
private interface BigDecimalMixin
@ -191,77 +172,6 @@ object JacksonSupport {
}
}
@ToStringSerialize
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
private interface NetworkHostAndPortMixin
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text)
}
}
@JsonSerialize(using = PartyAndCertificateSerializer::class)
// TODO Add deserialization which follows the same lookup logic as Party
private interface PartyAndCertificateSerializerMixin
private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject {
writeObjectField("name", value.name)
writeObjectField("owningKey", value.owningKey)
// TODO Add configurable option to output the certPath
}
}
}
@JsonSerialize(using = SignedTransactionSerializer::class)
@JsonDeserialize(using = SignedTransactionDeserializer::class)
private interface SignedTransactionMixin2
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
}
}
private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
}
}
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
@JsonSerialize(using = SerializedBytesSerializer::class)
@JsonDeserialize(using = SerializedBytesDeserializer::class)
private class SerializedBytesMixin
private class SerializedBytesSerializer : JsonSerializer<SerializedBytes<*>>() {
override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
val deserialized = value.deserialize<Any>()
gen.jsonObject {
writeStringField("class", deserialized.javaClass.name)
writeObjectField("deserialized", deserialized)
}
}
}
private class SerializedBytesDeserializer : JsonDeserializer<SerializedBytes<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes<Any> {
return if (parser.currentToken == JsonToken.START_OBJECT) {
val mapper = parser.codec as ObjectMapper
val json = parser.readValueAsTree<ObjectNode>()
val clazz = context.findClass(json["class"].textValue())
val pojo = mapper.convertValue(json["deserialized"], clazz)
pojo.serialize()
} else {
SerializedBytes(parser.binaryValue)
}
}
}
@ToStringSerialize
private interface X500PrincipalMixin
@ -348,13 +258,6 @@ object JacksonSupport {
}
}
@JsonDeserialize(using = PartyDeserializer::class)
private interface AbstractPartyMixin
@JsonSerialize(using = AnonymousPartySerializer::class)
@JsonDeserialize(using = AnonymousPartyDeserializer::class)
private interface AnonymousPartyMixin
@Deprecated("This is an internal class, do not use")
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
override fun serialize(value: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
@ -369,9 +272,6 @@ object JacksonSupport {
}
}
@JsonSerialize(using = PartySerializer::class)
private interface PartyMixin
@Deprecated("This is an internal class, do not use")
object PartySerializer : JsonSerializer<Party>() {
override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) {
@ -406,10 +306,6 @@ object JacksonSupport {
}
}
@ToStringSerialize
@JsonDeserialize(using = CordaX500NameDeserializer::class)
private interface CordaX500NameMixin
@Deprecated("This is an internal class, do not use")
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name {
@ -421,10 +317,6 @@ object JacksonSupport {
}
}
@JsonIgnoreProperties("legalIdentities") // This is already covered by legalIdentitiesAndCerts
@JsonDeserialize(using = NodeInfoDeserializer::class)
private interface NodeInfoMixin
@Deprecated("This is an internal class, do not use")
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): NodeInfo {
@ -434,10 +326,6 @@ object JacksonSupport {
}
}
@ToStringSerialize
@JsonDeserialize(using = SecureHashDeserializer::class)
private interface SecureHashSHA256Mixin
@Deprecated("This is an internal class, do not use")
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
@ -449,10 +337,6 @@ object JacksonSupport {
}
}
@JsonSerialize(using = PublicKeySerializer::class)
@JsonDeserialize(using = PublicKeyDeserializer::class)
private interface PublicKeyMixin
@Deprecated("This is an internal class, do not use")
object PublicKeySerializer : JsonSerializer<PublicKey>() {
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
@ -471,10 +355,6 @@ object JacksonSupport {
}
}
@ToStringSerialize
@JsonDeserialize(using = AmountDeserializer::class)
private interface AmountMixin
@Deprecated("This is an internal class, do not use")
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
@ -489,22 +369,6 @@ object JacksonSupport {
private data class CurrencyAmountWrapper(val quantity: Long, val token: Currency)
@JsonDeserialize(using = OpaqueBytesDeserializer::class)
private interface ByteSequenceMixin {
@Suppress("unused")
@JsonValue
fun copyBytes(): ByteArray
}
@JsonIgnoreProperties("offset", "size")
@JsonSerialize
@JsonDeserialize
private interface ByteSequenceWithPropertiesMixin {
@Suppress("unused")
@JsonValue(false)
fun copyBytes(): ByteArray
}
@Deprecated("This is an internal class, do not use")
object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): OpaqueBytes {

View File

@ -0,0 +1,194 @@
@file:Suppress("DEPRECATION")
package net.corda.client.jackson.internal
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import net.corda.client.jackson.JacksonSupport
import net.corda.core.contracts.Amount
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.*
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.constructorForDeserialization
import net.corda.serialization.internal.amqp.createSerializerFactoryFactory
import net.corda.serialization.internal.amqp.propertiesForSerialization
import java.security.PublicKey
class CordaModule : SimpleModule("corda-core") {
override fun setupModule(context: SetupContext) {
super.setupModule(context)
context.addBeanSerializerModifier(CordaSerializableBeanSerializerModifier())
context.setMixInAnnotations(PartyAndCertificate::class.java, PartyAndCertificateSerializerMixin::class.java)
context.setMixInAnnotations(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java)
context.setMixInAnnotations(CordaX500Name::class.java, CordaX500NameMixin::class.java)
context.setMixInAnnotations(Amount::class.java, AmountMixin::class.java)
context.setMixInAnnotations(AbstractParty::class.java, AbstractPartyMixin::class.java)
context.setMixInAnnotations(AnonymousParty::class.java, AnonymousPartyMixin::class.java)
context.setMixInAnnotations(Party::class.java, PartyMixin::class.java)
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java)
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin2::class.java)
context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java)
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
}
}
/**
* Use the same properties that AMQP serialization uses if the POJO is @CordaSerializable
*/
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
// We need a SerializerFactory when scanning for properties but don't actually use it so any will do
private val serializerFactory = SerializerFactory(AllWhitelist, Thread.currentThread().contextClassLoader)
override fun changeProperties(config: SerializationConfig,
beanDesc: BeanDescription,
beanProperties: MutableList<BeanPropertyWriter>): MutableList<BeanPropertyWriter> {
// TODO We're assuming here that Jackson gives us a superset of all the properties. Either confirm this or
// make sure the returned beanProperties are exactly the AMQP properties
if (beanDesc.beanClass.isAnnotationPresent(CordaSerializable::class.java)) {
val ctor = constructorForDeserialization(beanDesc.beanClass)
val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory).serializationOrder
beanProperties.removeIf { bean -> amqpProperties.none { amqp -> amqp.serializer.name == bean.name } }
}
return beanProperties
}
}
@ToStringSerialize
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
private interface NetworkHostAndPortMixin
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext) = NetworkHostAndPort.parse(parser.text)
}
@JsonSerialize(using = PartyAndCertificateSerializer::class)
// TODO Add deserialization which follows the same lookup logic as Party
private interface PartyAndCertificateSerializerMixin
private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject {
writeObjectField("name", value.name)
writeObjectField("owningKey", value.owningKey)
// TODO Add configurable option to output the certPath
}
}
}
@JsonSerialize(using = SignedTransactionSerializer::class)
@JsonDeserialize(using = SignedTransactionDeserializer::class)
private interface SignedTransactionMixin2
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
}
}
private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
}
}
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
@JsonSerialize(using = SerializedBytesSerializer::class)
@JsonDeserialize(using = SerializedBytesDeserializer::class)
private class SerializedBytesMixin
private class SerializedBytesSerializer : JsonSerializer<SerializedBytes<*>>() {
override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
val deserialized = value.deserialize<Any>()
gen.jsonObject {
writeStringField("class", deserialized.javaClass.name)
writeObjectField("deserialized", deserialized)
}
}
}
private class SerializedBytesDeserializer : JsonDeserializer<SerializedBytes<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes<Any> {
return if (parser.currentToken == JsonToken.START_OBJECT) {
val mapper = parser.codec as ObjectMapper
val json = parser.readValueAsTree<ObjectNode>()
val clazz = context.findClass(json["class"].textValue())
val pojo = mapper.convertValue(json["deserialized"], clazz)
pojo.serialize()
} else {
SerializedBytes(parser.binaryValue)
}
}
}
@JsonDeserialize(using = JacksonSupport.PartyDeserializer::class)
private interface AbstractPartyMixin
@JsonSerialize(using = JacksonSupport.AnonymousPartySerializer::class)
@JsonDeserialize(using = JacksonSupport.AnonymousPartyDeserializer::class)
private interface AnonymousPartyMixin
@JsonSerialize(using = JacksonSupport.PartySerializer::class)
private interface PartyMixin
@ToStringSerialize
@JsonDeserialize(using = JacksonSupport.CordaX500NameDeserializer::class)
private interface CordaX500NameMixin
@JsonDeserialize(using = JacksonSupport.NodeInfoDeserializer::class)
private interface NodeInfoMixin
@ToStringSerialize
@JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class)
private interface SecureHashSHA256Mixin
@JsonSerialize(using = JacksonSupport.PublicKeySerializer::class)
@JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::class)
private interface PublicKeyMixin
@ToStringSerialize
@JsonDeserialize(using = JacksonSupport.AmountDeserializer::class)
private interface AmountMixin
@JsonDeserialize(using = JacksonSupport.OpaqueBytesDeserializer::class)
private interface ByteSequenceMixin {
@Suppress("unused")
@JsonValue
fun copyBytes(): ByteArray
}
@JsonSerialize
@JsonDeserialize
private interface ByteSequenceWithPropertiesMixin {
@Suppress("unused")
@JsonValue(false)
fun copyBytes(): ByteArray
}

View File

@ -1,19 +1,14 @@
package net.corda.client.jackson.internal
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import com.fasterxml.jackson.module.kotlin.convertValue
inline fun <reified T : Any> SimpleModule.addSerAndDeser(serializer: JsonSerializer<in T>, deserializer: JsonDeserializer<T>) {
addSerializer(T::class.java, serializer)
addDeserializer(T::class.java, deserializer)
}
inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerator.() -> Unit) {
fieldName?.let { writeFieldName(it) }
writeStartObject()
@ -24,3 +19,7 @@ inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerato
inline fun <reified T> JsonParser.readValueAs(): T = readValueAs(T::class.java)
inline fun <reified T : Any> JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
@JacksonAnnotationsInside
@JsonSerialize(using = ToStringSerializer::class)
annotation class ToStringSerialize

View File

@ -405,6 +405,15 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
testToStringSerialisation(X500Principal("CN=Common,L=London,O=Org,C=UK"))
}
@Test
fun `@CordaSerializable class which has non-c'tor properties`() {
val data = NonCtorPropertiesData(4434)
val json = mapper.valueToTree<ObjectNode>(data)
val (value) = json.assertHasOnlyFields("value")
assertThat(value.intValue()).isEqualTo(4434)
assertThat(mapper.convertValue<NonCtorPropertiesData>(json)).isEqualTo(data)
}
private fun makeDummyStx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)
@ -432,6 +441,12 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@CordaSerializable
private data class SubTestData(val value: Int)
@CordaSerializable
private data class NonCtorPropertiesData(val value: Int) {
@Suppress("unused")
val nonCtor: Int get() = value
}
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
val identities = ArrayList<Party>()
val nodes = ArrayList<NodeInfo>()

View File

@ -74,7 +74,7 @@ class CorDappCustomSerializer(
data.withDescribed(descriptor) {
data.withList {
proxySerializer.propertySerializers.serializationOrder.forEach {
it.getter.writeProperty(proxy, this, output, context)
it.serializer.writeProperty(proxy, this, output, context)
}
}
}

View File

@ -157,7 +157,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
val proxy = toProxy(obj)
data.withList {
proxySerializer.propertySerializers.serializationOrder.forEach {
it.getter.writeProperty(proxy, this, output, context)
it.serializer.writeProperty(proxy, this, output, context)
}
}
}

View File

@ -1,5 +1,6 @@
package net.corda.serialization.internal.amqp
import net.corda.core.internal.isConcreteClass
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.carpenter.getTypeAsClass
@ -63,7 +64,7 @@ abstract class EvolutionSerializer(
private fun getEvolverConstructor(type: Type, oldArgs: Map<String, OldParam>): KFunction<Any>? {
val clazz: Class<*> = type.asClass()!!
if (!isConcrete(clazz)) return null
if (!clazz.isConcreteClass) return null
val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.property.resolvedType) }
@ -109,7 +110,7 @@ abstract class EvolutionSerializer(
classProperties: Map<String, PropertyDescriptor>): AMQPSerializer<Any> {
val setters = propertiesForSerializationFromSetters(classProperties,
new.type,
factory).associateBy({ it.getter.name }, { it })
factory).associateBy({ it.serializer.name }, { it })
return EvolutionSerializerViaSetters(new.type, factory, readersAsSerialized, constructor, setters)
}

View File

@ -192,9 +192,9 @@ class SerializerFingerPrinter : FingerPrinter {
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
.serializationOrder
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, debugIndent + 1)
.putUnencodedChars(prop.getter.name)
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
fingerprintForType(prop.serializer.resolvedType, type, alreadySeen, orig, debugIndent + 1)
.putUnencodedChars(prop.serializer.name)
.putUnencodedChars(if (prop.serializer.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, debugIndent + 1) }
return hasher

View File

@ -23,12 +23,10 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val logger = contextLogger()
}
internal open val propertySerializers: PropertySerializers by lazy {
open val propertySerializers: PropertySerializers by lazy {
propertiesForSerialization(kotlinConstructor, clazz, factory)
}
fun getPropertySerializers() = propertySerializers
private val typeName = nameForType(clazz)
override val typeDescriptor = Symbol.valueOf(
@ -48,7 +46,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
}
propertySerializers.serializationOrder.forEach { property ->
property.getter.writeClassInfo(output)
property.serializer.writeClassInfo(output)
}
}
}
@ -74,7 +72,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
// Write list
withList {
propertySerializers.serializationOrder.forEach { property ->
property.getter.writeProperty(obj, this, output, context, debugIndent + 1)
property.serializer.writeProperty(obj, this, output, context, debugIndent + 1)
}
}
}
@ -109,7 +107,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
return construct(propertySerializers.serializationOrder
.zip(obj)
.map { Pair(it.first.initialPosition, it.first.getter.readProperty(it.second, schemas, input, context)) }
.map { Pair(it.first.initialPosition, it.first.serializer.readProperty(it.second, schemas, input, context)) }
.sortedWith(compareBy({ it.first }))
.map { it.second })
}
@ -128,7 +126,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
// do it in doesn't matter
val propertiesFromBlob = obj
.zip(propertySerializers.serializationOrder)
.map { it.second.getter.readProperty(it.first, schemas, input, context) }
.map { it.second.serializer.readProperty(it.first, schemas, input, context) }
// one by one take a property and invoke the setter on the class
propertySerializers.serializationOrder.zip(propertiesFromBlob).forEach {
@ -140,7 +138,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private fun generateFields(): List<Field> {
return propertySerializers.serializationOrder.map {
Field(it.getter.name, it.getter.type, it.getter.requires, it.getter.default, null, it.getter.mandatory, false)
Field(it.serializer.name, it.serializer.type, it.serializer.requires, it.serializer.default, null, it.serializer.mandatory, false)
}
}

View File

@ -104,16 +104,16 @@ class EvolutionPropertyReader : PropertyReader() {
* Represents a generic interface to a serializable property of an object.
*
* @property initialPosition where in the constructor used for serialization the property occurs.
* @property getter a [PropertySerializer] wrapping access to the property. This will either be a
* @property serializer a [PropertySerializer] wrapping access to the property. This will either be a
* method invocation on the getter or, if not publicly accessible, reflection based by temporally
* making the property accessible.
*/
abstract class PropertyAccessor(
val initialPosition: Int,
open val getter: PropertySerializer) {
open val serializer: PropertySerializer) {
companion object : Comparator<PropertyAccessor> {
override fun compare(p0: PropertyAccessor?, p1: PropertyAccessor?): Int {
return p0?.getter?.name?.compareTo(p1?.getter?.name ?: "") ?: 0
return p0?.serializer?.name?.compareTo(p1?.serializer?.name ?: "") ?: 0
}
}
@ -123,7 +123,7 @@ abstract class PropertyAccessor(
abstract fun set(instance: Any, obj: Any?)
override fun toString(): String {
return "${getter.name}($initialPosition)"
return "${serializer.name}($initialPosition)"
}
}
@ -157,7 +157,7 @@ class PropertyAccessorGetterSetter(
*/
class PropertyAccessorConstructor(
initialPosition: Int,
override val getter: PropertySerializer) : PropertyAccessor(initialPosition, getter) {
override val serializer: PropertySerializer) : PropertyAccessor(initialPosition, serializer) {
/**
* Because the property should be being set on the obejct through the constructor any
* calls to the explicit setter should be an error.

View File

@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken
import net.corda.core.internal.isConcreteClass
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
@ -25,11 +26,11 @@ import kotlin.reflect.jvm.javaType
*
* If there's only one constructor, it selects that. If there are two and one is the default, it selects the other.
* Otherwise it starts with the primary constructor in kotlin, if there is one, and then will override this with any that is
* annotated with [@CordaConstructor]. It will report an error if more than one constructor is annotated.
* annotated with [@ConstructorForDeserialization]. It will report an error if more than one constructor is annotated.
*/
internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
fun constructorForDeserialization(type: Type): KFunction<Any>? {
val clazz: Class<*> = type.asClass()!!
if (isConcrete(clazz)) {
if (clazz.isConcreteClass) {
var preferredCandidate: KFunction<Any>? = clazz.kotlin.primaryConstructor
var annotatedCount = 0
val kotlinConstructors = clazz.kotlin.constructors
@ -42,7 +43,7 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
preferredCandidate = kotlinConstructor
} else if (kotlinConstructor.findAnnotation<ConstructorForDeserialization>() != null) {
if (annotatedCount++ > 0) {
throw NotSerializableException("More than one constructor for $clazz is annotated with @CordaConstructor.")
throw NotSerializableException("More than one constructor for $clazz is annotated with @ConstructorForDeserialization.")
}
preferredCandidate = kotlinConstructor
}
@ -63,18 +64,18 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
* Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters
* have names accessible via reflection.
*/
internal fun <T : Any> propertiesForSerialization(
fun <T : Any> propertiesForSerialization(
kotlinConstructor: KFunction<T>?,
type: Type,
factory: SerializerFactory) = PropertySerializers.make(
if (kotlinConstructor != null) {
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
} else {
propertiesForSerializationFromAbstract(type.asClass()!!, type, factory)
}.sortedWith(PropertyAccessor))
fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
factory: SerializerFactory): PropertySerializers {
return PropertySerializers.make(
if (kotlinConstructor != null) {
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
} else {
propertiesForSerializationFromAbstract(type.asClass()!!, type, factory)
}.sortedWith(PropertyAccessor)
)
}
/**
* Encapsulates the property of a class and its potential getter and setter methods.

View File

@ -24,7 +24,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
try {
val constructor = constructorForDeserialization(obj.javaClass)
propertiesForSerializationFromConstructor(constructor!!, obj.javaClass, factory).forEach { property ->
extraProperties[property.getter.name] = property.getter.propertyReader.read(obj)
extraProperties[property.serializer.name] = property.serializer.propertyReader.read(obj)
}
} catch (e: NotSerializableException) {
logger.warn("Unexpected exception", e)

View File

@ -160,7 +160,7 @@ public class JavaPrivatePropertyTests {
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
assertTrue (((PropertyAccessor)propertyReaders[0]).getGetter().getPropertyReader() instanceof PrivatePropertyReader);
assertTrue (((PropertyAccessor)propertyReaders[0]).getSerializer().getPropertyReader() instanceof PrivatePropertyReader);
}
@Test
@ -190,6 +190,6 @@ public class JavaPrivatePropertyTests {
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
assertTrue (((PropertyAccessor)propertyReaders[0]).getGetter().getPropertyReader() instanceof PublicPropertyReader);
assertTrue (((PropertyAccessor)propertyReaders[0]).getSerializer().getPropertyReader() instanceof PublicPropertyReader);
}
}

View File

@ -141,7 +141,7 @@ class PrivatePropertyTests {
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer)
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.getter }
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.serializer }
assertEquals(2, propertySerializers.size)
// a was public so should have a synthesised getter
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
@ -170,7 +170,7 @@ class PrivatePropertyTests {
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer)
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.getter }
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.serializer }
assertEquals(2, propertySerializers.size)
// as before, a is public so we'll use the getter method
@ -219,7 +219,7 @@ class PrivatePropertyTests {
assertEquals(1, size)
assertTrue(this.first() is ObjectSerializer)
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.getter }
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.serializer }
// CCC is the only property to be serialised
assertEquals(1, propertySerializers.size)