mirror of
https://github.com/corda/corda.git
synced 2025-06-14 13:18:18 +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:
@ -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"
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
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
|
@ -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>()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user