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
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' apply plugin: 'com.jfrog.artifactory'
dependencies { dependencies {
compile project(':core') compile project(':serialization')
testCompile project(':test-utils') testCompile project(':test-utils')
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

View File

@ -1,16 +1,17 @@
package net.corda.client.jackson 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.core.*
import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers 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.databind.node.ObjectNode
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule 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.jsonObject
import net.corda.client.jackson.internal.readValueAs import net.corda.client.jackson.internal.readValueAs
import net.corda.core.CordaInternal 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.ContractState
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.TransactionSignature import net.corda.core.identity.AbstractParty
import net.corda.core.identity.* 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.CertRole
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction 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 org.bouncycastle.asn1.x509.KeyPurposeId
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.math.BigDecimal import java.math.BigDecimal
@ -93,31 +95,9 @@ object JacksonSupport {
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null
} }
val cordaModule: Module by lazy { @Suppress("unused")
SimpleModule("core").apply { @Deprecated("Do not use this as it's not thread safe. Instead get a ObjectMapper instance with one of the create*Mapper methods.")
setMixInAnnotation(BigDecimal::class.java, BigDecimalMixin::class.java) val cordaModule: Module by lazy(::CordaModule)
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)
}
}
/** /**
* Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names. * Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names.
@ -172,14 +152,15 @@ object JacksonSupport {
registerModule(JavaTimeModule().apply { registerModule(JavaTimeModule().apply {
addSerializer(Date::class.java, DateSerializer) addSerializer(Date::class.java, DateSerializer)
}) })
registerModule(cordaModule) registerModule(CordaModule())
registerModule(KotlinModule()) registerModule(KotlinModule())
}
}
@JacksonAnnotationsInside addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java)
@JsonSerialize(using = com.fasterxml.jackson.databind.ser.std.ToStringSerializer::class) addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java)
private annotation class ToStringSerialize addMixIn(X509Certificate::class.java, X509CertificateMixin::class.java)
addMixIn(CertPath::class.java, CertPathMixin::class.java)
}
}
@ToStringSerialize @ToStringSerialize
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class) @JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
@ -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 @ToStringSerialize
private interface X500PrincipalMixin 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") @Deprecated("This is an internal class, do not use")
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() { object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
override fun serialize(value: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) { 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") @Deprecated("This is an internal class, do not use")
object PartySerializer : JsonSerializer<Party>() { object PartySerializer : JsonSerializer<Party>() {
override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) { 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") @Deprecated("This is an internal class, do not use")
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() { object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): 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") @Deprecated("This is an internal class, do not use")
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() { object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): 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") @Deprecated("This is an internal class, do not use")
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() { class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): 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") @Deprecated("This is an internal class, do not use")
object PublicKeySerializer : JsonSerializer<PublicKey>() { object PublicKeySerializer : JsonSerializer<PublicKey>() {
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) { 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") @Deprecated("This is an internal class, do not use")
object AmountDeserializer : JsonDeserializer<Amount<*>>() { object AmountDeserializer : JsonDeserializer<Amount<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): 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) 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") @Deprecated("This is an internal class, do not use")
object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() { object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): 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 package net.corda.client.jackson.internal
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside
import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper 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 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) { inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerator.() -> Unit) {
fieldName?.let { writeFieldName(it) } fieldName?.let { writeFieldName(it) }
writeStartObject() 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> JsonParser.readValueAs(): T = readValueAs(T::class.java)
inline fun <reified T : Any> JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this) 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")) 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 { private fun makeDummyStx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services) .toWireTransaction(services)
@ -432,6 +441,12 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@CordaSerializable @CordaSerializable
private data class SubTestData(val value: Int) 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 { private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
val identities = ArrayList<Party>() val identities = ArrayList<Party>()
val nodes = ArrayList<NodeInfo>() val nodes = ArrayList<NodeInfo>()

View File

@ -74,7 +74,7 @@ class CorDappCustomSerializer(
data.withDescribed(descriptor) { data.withDescribed(descriptor) {
data.withList { data.withList {
proxySerializer.propertySerializers.serializationOrder.forEach { 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) val proxy = toProxy(obj)
data.withList { data.withList {
proxySerializer.propertySerializers.serializationOrder.forEach { 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 package net.corda.serialization.internal.amqp
import net.corda.core.internal.isConcreteClass
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.carpenter.getTypeAsClass 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>? { private fun getEvolverConstructor(type: Type, oldArgs: Map<String, OldParam>): KFunction<Any>? {
val clazz: Class<*> = type.asClass()!! 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) } 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> { classProperties: Map<String, PropertyDescriptor>): AMQPSerializer<Any> {
val setters = propertiesForSerializationFromSetters(classProperties, val setters = propertiesForSerializationFromSetters(classProperties,
new.type, new.type,
factory).associateBy({ it.getter.name }, { it }) factory).associateBy({ it.serializer.name }, { it })
return EvolutionSerializerViaSetters(new.type, factory, readersAsSerialized, constructor, setters) return EvolutionSerializerViaSetters(new.type, factory, readersAsSerialized, constructor, setters)
} }

View File

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

View File

@ -23,12 +23,10 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val logger = contextLogger() private val logger = contextLogger()
} }
internal open val propertySerializers: PropertySerializers by lazy { open val propertySerializers: PropertySerializers by lazy {
propertiesForSerialization(kotlinConstructor, clazz, factory) propertiesForSerialization(kotlinConstructor, clazz, factory)
} }
fun getPropertySerializers() = propertySerializers
private val typeName = nameForType(clazz) private val typeName = nameForType(clazz)
override val typeDescriptor = Symbol.valueOf( override val typeDescriptor = Symbol.valueOf(
@ -48,7 +46,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
} }
propertySerializers.serializationOrder.forEach { property -> 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 // Write list
withList { withList {
propertySerializers.serializationOrder.forEach { property -> 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 return construct(propertySerializers.serializationOrder
.zip(obj) .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 })) .sortedWith(compareBy({ it.first }))
.map { it.second }) .map { it.second })
} }
@ -128,7 +126,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
// do it in doesn't matter // do it in doesn't matter
val propertiesFromBlob = obj val propertiesFromBlob = obj
.zip(propertySerializers.serializationOrder) .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 // one by one take a property and invoke the setter on the class
propertySerializers.serializationOrder.zip(propertiesFromBlob).forEach { propertySerializers.serializationOrder.zip(propertiesFromBlob).forEach {
@ -140,7 +138,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private fun generateFields(): List<Field> { private fun generateFields(): List<Field> {
return propertySerializers.serializationOrder.map { 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. * Represents a generic interface to a serializable property of an object.
* *
* @property initialPosition where in the constructor used for serialization the property occurs. * @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 * method invocation on the getter or, if not publicly accessible, reflection based by temporally
* making the property accessible. * making the property accessible.
*/ */
abstract class PropertyAccessor( abstract class PropertyAccessor(
val initialPosition: Int, val initialPosition: Int,
open val getter: PropertySerializer) { open val serializer: PropertySerializer) {
companion object : Comparator<PropertyAccessor> { companion object : Comparator<PropertyAccessor> {
override fun compare(p0: PropertyAccessor?, p1: PropertyAccessor?): Int { 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?) abstract fun set(instance: Any, obj: Any?)
override fun toString(): String { override fun toString(): String {
return "${getter.name}($initialPosition)" return "${serializer.name}($initialPosition)"
} }
} }
@ -157,7 +157,7 @@ class PropertyAccessorGetterSetter(
*/ */
class PropertyAccessorConstructor( class PropertyAccessorConstructor(
initialPosition: Int, 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 * Because the property should be being set on the obejct through the constructor any
* calls to the explicit setter should be an error. * 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.primitives.Primitives
import com.google.common.reflect.TypeToken import com.google.common.reflect.TypeToken
import net.corda.core.internal.isConcreteClass
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable 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. * 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 * 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()!! val clazz: Class<*> = type.asClass()!!
if (isConcrete(clazz)) { if (clazz.isConcreteClass) {
var preferredCandidate: KFunction<Any>? = clazz.kotlin.primaryConstructor var preferredCandidate: KFunction<Any>? = clazz.kotlin.primaryConstructor
var annotatedCount = 0 var annotatedCount = 0
val kotlinConstructors = clazz.kotlin.constructors val kotlinConstructors = clazz.kotlin.constructors
@ -42,7 +43,7 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
preferredCandidate = kotlinConstructor preferredCandidate = kotlinConstructor
} else if (kotlinConstructor.findAnnotation<ConstructorForDeserialization>() != null) { } else if (kotlinConstructor.findAnnotation<ConstructorForDeserialization>() != null) {
if (annotatedCount++ > 0) { 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 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 * Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters
* have names accessible via reflection. * have names accessible via reflection.
*/ */
internal fun <T : Any> propertiesForSerialization( fun <T : Any> propertiesForSerialization(
kotlinConstructor: KFunction<T>?, kotlinConstructor: KFunction<T>?,
type: Type, type: Type,
factory: SerializerFactory) = PropertySerializers.make( factory: SerializerFactory): PropertySerializers {
return PropertySerializers.make(
if (kotlinConstructor != null) { if (kotlinConstructor != null) {
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
} else { } else {
propertiesForSerializationFromAbstract(type.asClass()!!, type, factory) propertiesForSerializationFromAbstract(type.asClass()!!, type, factory)
}.sortedWith(PropertyAccessor)) }.sortedWith(PropertyAccessor)
)
}
fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
/** /**
* Encapsulates the property of a class and its potential getter and setter methods. * 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 { try {
val constructor = constructorForDeserialization(obj.javaClass) val constructor = constructorForDeserialization(obj.javaClass)
propertiesForSerializationFromConstructor(constructor!!, obj.javaClass, factory).forEach { property -> 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) { } catch (e: NotSerializableException) {
logger.warn("Unexpected exception", e) logger.warn("Unexpected exception", e)

View File

@ -160,7 +160,7 @@ public class JavaPrivatePropertyTests {
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]); ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size()); assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray(); Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
assertTrue (((PropertyAccessor)propertyReaders[0]).getGetter().getPropertyReader() instanceof PrivatePropertyReader); assertTrue (((PropertyAccessor)propertyReaders[0]).getSerializer().getPropertyReader() instanceof PrivatePropertyReader);
} }
@Test @Test
@ -190,6 +190,6 @@ public class JavaPrivatePropertyTests {
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]); ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size()); assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray(); 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 { serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size) assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer) 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) assertEquals(2, propertySerializers.size)
// a was public so should have a synthesised getter // a was public so should have a synthesised getter
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader) assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
@ -170,7 +170,7 @@ class PrivatePropertyTests {
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply { serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size) assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer) 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) assertEquals(2, propertySerializers.size)
// as before, a is public so we'll use the getter method // as before, a is public so we'll use the getter method
@ -219,7 +219,7 @@ class PrivatePropertyTests {
assertEquals(1, size) assertEquals(1, size)
assertTrue(this.first() is ObjectSerializer) 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 // CCC is the only property to be serialised
assertEquals(1, propertySerializers.size) assertEquals(1, propertySerializers.size)