mirror of
https://github.com/corda/corda.git
synced 2025-02-20 09:26:41 +00:00
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:
parent
3cdd908714
commit
824adca6c0
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
@ -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
|
@ -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>()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user