mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-1238: Updated JacksonSupport to support SerializedBytes, CertPath, X509Certificate and the signature classes (#3145)
SerializedBytes are first converted to the object it represents before being serialised as a pojo. These changes will be needed to support the the blob inspector when it will output to YAML/JSON.
This commit is contained in:
parent
7da0bcd741
commit
b031e66ab9
117
.idea/compiler.xml
generated
117
.idea/compiler.xml
generated
@ -4,28 +4,80 @@
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="api-scanner_main" target="1.8" />
|
||||
<module name="api-scanner_test" target="1.8" />
|
||||
<module name="attachment-demo_integrationTest" target="1.8" />
|
||||
<module name="attachment-demo_main" target="1.8" />
|
||||
<module name="attachment-demo_test" target="1.8" />
|
||||
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
||||
<module name="bank-of-corda-demo_main" target="1.8" />
|
||||
<module name="bank-of-corda-demo_test" target="1.8" />
|
||||
<module name="behave_behave" target="1.8" />
|
||||
<module name="behave_main" target="1.8" />
|
||||
<module name="behave_scenario" target="1.8" />
|
||||
<module name="behave_test" target="1.8" />
|
||||
<module name="blobinspector_main" target="1.8" />
|
||||
<module name="blobinspector_test" target="1.8" />
|
||||
<module name="bootstrapper_main" target="1.8" />
|
||||
<module name="bootstrapper_test" target="1.8" />
|
||||
<module name="buildSrc_main" target="1.8" />
|
||||
<module name="buildSrc_test" target="1.8" />
|
||||
<module name="canonicalizer_main" target="1.8" />
|
||||
<module name="canonicalizer_test" target="1.8" />
|
||||
<module name="client_main" target="1.8" />
|
||||
<module name="client_test" target="1.8" />
|
||||
<module name="confidential-identities_main" target="1.8" />
|
||||
<module name="confidential-identities_test" target="1.8" />
|
||||
<module name="contracts-states_integrationTest" target="1.8" />
|
||||
<module name="contracts-states_main" target="1.8" />
|
||||
<module name="contracts-states_test" target="1.8" />
|
||||
<module name="corda-core_integrationTest" target="1.8" />
|
||||
<module name="corda-core_smokeTest" target="1.8" />
|
||||
<module name="corda-finance_integrationTest" target="1.8" />
|
||||
<module name="corda-project_main" target="1.8" />
|
||||
<module name="corda-project_test" target="1.8" />
|
||||
<module name="corda-webserver_integrationTest" target="1.8" />
|
||||
<module name="corda-webserver_main" target="1.8" />
|
||||
<module name="corda-webserver_test" target="1.8" />
|
||||
<module name="cordapp-configuration_main" target="1.8" />
|
||||
<module name="cordapp-configuration_test" target="1.8" />
|
||||
<module name="cordapp_integrationTest" target="1.8" />
|
||||
<module name="cordform-common_main" target="1.8" />
|
||||
<module name="cordform-common_test" target="1.8" />
|
||||
<module name="cordformation_main" target="1.8" />
|
||||
<module name="cordformation_runnodes" target="1.8" />
|
||||
<module name="cordformation_test" target="1.8" />
|
||||
<module name="core_extraResource" target="1.8" />
|
||||
<module name="core_integrationTest" target="1.8" />
|
||||
<module name="core_main" target="1.8" />
|
||||
<module name="core_smokeTest" target="1.8" />
|
||||
<module name="core_test" target="1.8" />
|
||||
<module name="demobench_main" target="1.8" />
|
||||
<module name="demobench_test" target="1.8" />
|
||||
<module name="docs_main" target="1.8" />
|
||||
<module name="docs_source_example-code_integrationTest" target="1.8" />
|
||||
<module name="docs_source_example-code_main" target="1.8" />
|
||||
<module name="docs_source_example-code_test" target="1.8" />
|
||||
<module name="docs_test" target="1.8" />
|
||||
<module name="example-code_integrationTest" target="1.8" />
|
||||
<module name="example-code_main" target="1.8" />
|
||||
<module name="example-code_test" target="1.8" />
|
||||
<module name="experimental-kryo-hook_main" target="1.8" />
|
||||
<module name="experimental-kryo-hook_test" target="1.8" />
|
||||
<module name="experimental_main" target="1.8" />
|
||||
<module name="experimental_test" target="1.8" />
|
||||
<module name="explorer-capsule_main" target="1.6" />
|
||||
<module name="explorer-capsule_test" target="1.6" />
|
||||
<module name="explorer_main" target="1.8" />
|
||||
<module name="explorer_test" target="1.8" />
|
||||
<module name="finance_integrationTest" target="1.8" />
|
||||
<module name="finance_main" target="1.8" />
|
||||
<module name="finance_test" target="1.8" />
|
||||
<module name="flows_integrationTest" target="1.8" />
|
||||
<module name="flows_main" target="1.8" />
|
||||
<module name="flows_test" target="1.8" />
|
||||
<module name="gradle-plugins-cordapp_main" target="1.8" />
|
||||
<module name="gradle-plugins-cordapp_test" target="1.8" />
|
||||
<module name="graphs_main" target="1.8" />
|
||||
<module name="graphs_test" target="1.8" />
|
||||
<module name="irs-demo-cordapp_integrationTest" target="1.8" />
|
||||
<module name="irs-demo-cordapp_main" target="1.8" />
|
||||
<module name="irs-demo-cordapp_main~1" target="1.8" />
|
||||
@ -33,15 +85,68 @@
|
||||
<module name="irs-demo-cordapp_test~1" target="1.8" />
|
||||
<module name="irs-demo-web_main" target="1.8" />
|
||||
<module name="irs-demo-web_test" target="1.8" />
|
||||
<module name="irs-demo_integrationTest" target="1.8" />
|
||||
<module name="irs-demo_main" target="1.8" />
|
||||
<module name="irs-demo_systemTest" target="1.8" />
|
||||
<module name="irs-demo_test" target="1.8" />
|
||||
<module name="isolated_main" target="1.8" />
|
||||
<module name="isolated_test" target="1.8" />
|
||||
<module name="jackson_main" target="1.8" />
|
||||
<module name="jackson_test" target="1.8" />
|
||||
<module name="jfx_integrationTest" target="1.8" />
|
||||
<module name="jfx_main" target="1.8" />
|
||||
<module name="jfx_test" target="1.8" />
|
||||
<module name="kryo-hook_main" target="1.8" />
|
||||
<module name="kryo-hook_test" target="1.8" />
|
||||
<module name="loadtest_main" target="1.8" />
|
||||
<module name="loadtest_test" target="1.8" />
|
||||
<module name="mock_main" target="1.8" />
|
||||
<module name="mock_test" target="1.8" />
|
||||
<module name="network-visualiser_main" target="1.8" />
|
||||
<module name="network-visualiser_test" target="1.8" />
|
||||
<module name="node-api_main" target="1.8" />
|
||||
<module name="node-api_test" target="1.8" />
|
||||
<module name="node-capsule_main" target="1.6" />
|
||||
<module name="node-capsule_test" target="1.6" />
|
||||
<module name="node-driver_integrationTest" target="1.8" />
|
||||
<module name="node-driver_main" target="1.8" />
|
||||
<module name="node-driver_test" target="1.8" />
|
||||
<module name="node_integrationTest" target="1.8" />
|
||||
<module name="node_main" target="1.8" />
|
||||
<module name="node_smokeTest" target="1.8" />
|
||||
<module name="node_test" target="1.8" />
|
||||
<module name="notary-demo_main" target="1.8" />
|
||||
<module name="notary-demo_test" target="1.8" />
|
||||
<module name="publish-utils_main" target="1.8" />
|
||||
<module name="publish-utils_test" target="1.8" />
|
||||
<module name="quasar-hook_main" target="1.8" />
|
||||
<module name="quasar-hook_test" target="1.8" />
|
||||
<module name="quasar-utils_main" target="1.8" />
|
||||
<module name="quasar-utils_test" target="1.8" />
|
||||
<module name="rpc_integrationTest" target="1.8" />
|
||||
<module name="rpc_main" target="1.8" />
|
||||
<module name="rpc_smokeTest" target="1.8" />
|
||||
<module name="rpc_test" target="1.8" />
|
||||
<module name="samples_main" target="1.8" />
|
||||
<module name="samples_test" target="1.8" />
|
||||
<module name="sandbox_main" target="1.8" />
|
||||
<module name="sandbox_test" target="1.8" />
|
||||
<module name="shell_integrationTest" target="1.8" />
|
||||
<module name="shell_main" target="1.8" />
|
||||
<module name="shell_test" target="1.8" />
|
||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_main" target="1.8" />
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
<module name="smoke-test-utils_main" target="1.8" />
|
||||
<module name="smoke-test-utils_test" target="1.8" />
|
||||
<module name="source-example-code_integrationTest" target="1.8" />
|
||||
<module name="source-example-code_main" target="1.8" />
|
||||
<module name="source-example-code_test" target="1.8" />
|
||||
<module name="test-common_main" target="1.8" />
|
||||
<module name="test-common_test" target="1.8" />
|
||||
<module name="test-utils_integrationTest" target="1.8" />
|
||||
<module name="test-utils_main" target="1.8" />
|
||||
<module name="test-utils_test" target="1.8" />
|
||||
<module name="testing-node-driver_integrationTest" target="1.8" />
|
||||
<module name="testing-node-driver_main" target="1.8" />
|
||||
<module name="testing-node-driver_test" target="1.8" />
|
||||
@ -51,11 +156,23 @@
|
||||
<module name="testing-test-common_test" target="1.8" />
|
||||
<module name="testing-test-utils_main" target="1.8" />
|
||||
<module name="testing-test-utils_test" target="1.8" />
|
||||
<module name="tools_main" target="1.8" />
|
||||
<module name="tools_test" target="1.8" />
|
||||
<module name="trader-demo_integrationTest" target="1.8" />
|
||||
<module name="trader-demo_main" target="1.8" />
|
||||
<module name="trader-demo_test" target="1.8" />
|
||||
<module name="verifier_integrationTest" target="1.8" />
|
||||
<module name="verifier_main" target="1.8" />
|
||||
<module name="verifier_test" target="1.8" />
|
||||
<module name="web_main" target="1.8" />
|
||||
<module name="web_test" target="1.8" />
|
||||
<module name="webcapsule_main" target="1.6" />
|
||||
<module name="webcapsule_test" target="1.6" />
|
||||
<module name="webserver-webcapsule_main" target="1.8" />
|
||||
<module name="webserver-webcapsule_test" target="1.8" />
|
||||
<module name="webserver_integrationTest" target="1.8" />
|
||||
<module name="webserver_main" target="1.8" />
|
||||
<module name="webserver_test" target="1.8" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
|
@ -1,21 +1,20 @@
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.annotation.*
|
||||
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.deser.std.StdDeserializer
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
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.addSerAndDeser
|
||||
import net.corda.client.jackson.internal.jsonObject
|
||||
import net.corda.client.jackson.internal.readValueAs
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.CordaOID
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.ContractState
|
||||
@ -23,24 +22,30 @@ 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.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.NetworkHostAndPort
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.parsePublicKeyBase58
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.utilities.*
|
||||
import org.bouncycastle.asn1.x509.KeyPurposeId
|
||||
import java.lang.reflect.Modifier
|
||||
import java.math.BigDecimal
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
/**
|
||||
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
|
||||
@ -90,25 +95,26 @@ object JacksonSupport {
|
||||
|
||||
val cordaModule: Module by lazy {
|
||||
SimpleModule("core").apply {
|
||||
addSerAndDeser(AnonymousPartySerializer, AnonymousPartyDeserializer)
|
||||
addSerAndDeser(PartySerializer, PartyDeserializer)
|
||||
addDeserializer(AbstractParty::class.java, PartyDeserializer)
|
||||
addSerAndDeser<BigDecimal>(toStringSerializer, NumberDeserializers.BigDecimalDeserializer())
|
||||
addSerAndDeser<SecureHash.SHA256>(toStringSerializer, SecureHashDeserializer())
|
||||
addSerAndDeser(toStringSerializer, AmountDeserializer)
|
||||
addSerAndDeser(OpaqueBytesSerializer, OpaqueBytesDeserializer)
|
||||
addSerAndDeser(toStringSerializer, CordaX500NameDeserializer)
|
||||
addSerAndDeser(PublicKeySerializer, PublicKeyDeserializer)
|
||||
addDeserializer(CompositeKey::class.java, CompositeKeyDeseriaizer)
|
||||
addSerAndDeser(toStringSerializer, NetworkHostAndPortDeserializer)
|
||||
// TODO Add deserialization which follows the same lookup logic as Party
|
||||
addSerializer(PartyAndCertificate::class.java, PartyAndCertificateSerializer)
|
||||
addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
|
||||
|
||||
listOf(TransactionSignatureSerde, SignedTransactionSerde).forEach { serde -> serde.applyTo(this) }
|
||||
|
||||
// Using mixins to fine-tune the default serialised output
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -171,7 +177,13 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private val toStringSerializer = com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = com.fasterxml.jackson.databind.ser.std.ToStringSerializer::class)
|
||||
private annotation class ToStringSerialize
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
|
||||
private interface BigDecimalMixin
|
||||
|
||||
private object DateSerializer : JsonSerializer<Date>() {
|
||||
override fun serialize(value: Date, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
@ -179,20 +191,21 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private object NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
private object CompositeKeyDeseriaizer : JsonDeserializer<CompositeKey>() {
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CompositeKey {
|
||||
val publicKey = parser.readValueAs<PublicKey>()
|
||||
return publicKey as? CompositeKey ?: throw JsonParseException(parser, "Not a CompositeKey: $publicKey")
|
||||
}
|
||||
}
|
||||
@JsonSerialize(using = PartyAndCertificateSerializer::class)
|
||||
// TODO Add deserialization which follows the same lookup logic as Party
|
||||
private interface PartyAndCertificateSerializerMixin
|
||||
|
||||
private object PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
|
||||
private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
|
||||
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.jsonObject {
|
||||
writeObjectField("name", value.name)
|
||||
@ -202,100 +215,146 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private interface NodeInfoMixin {
|
||||
@get:JsonIgnore val legalIdentities: Any // This is already covered by legalIdentitiesAndCerts
|
||||
@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 interface JsonSerde<TYPE> {
|
||||
val type: Class<TYPE>
|
||||
val serializer: JsonSerializer<TYPE>
|
||||
val deserializer: JsonDeserializer<TYPE>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyTo(module: SimpleModule) {
|
||||
with(module) {
|
||||
addSerializer(type, serializer)
|
||||
addDeserializer(type, deserializer)
|
||||
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 inline fun <reified RESULT> JsonNode.get(fieldName: String, condition: (JsonNode) -> Boolean, mapper: ObjectMapper, parser: JsonParser): RESULT {
|
||||
if (get(fieldName)?.let(condition) != true) {
|
||||
JsonParseException(parser, "Missing required object field \"$fieldName\".")
|
||||
}
|
||||
return mapper.treeToValue(get(fieldName), RESULT::class.java)
|
||||
}
|
||||
|
||||
private object TransactionSignatureSerde : JsonSerde<TransactionSignature> {
|
||||
override val type: Class<TransactionSignature> = TransactionSignature::class.java
|
||||
|
||||
override val serializer = object : StdSerializer<TransactionSignature>(type) {
|
||||
override fun serialize(value: TransactionSignature, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.jsonObject {
|
||||
writeObjectField("by", value.by)
|
||||
writeObjectField("signatureMetadata", value.signatureMetadata)
|
||||
writeObjectField("bytes", value.bytes)
|
||||
writeObjectField("partialMerkleTree", value.partialMerkleTree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val deserializer = object : StdDeserializer<TransactionSignature>(type) {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): TransactionSignature {
|
||||
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 = mapper.readTree<JsonNode>(parser)
|
||||
val by = mapper.convertValue<PublicKey>(json["by"])
|
||||
val signatureMetadata = json.get<SignatureMetadata>("signatureMetadata", JsonNode::isObject, mapper, parser)
|
||||
val bytes = json.get<ByteArray>("bytes", JsonNode::isObject, mapper, parser)
|
||||
val partialMerkleTree = json.get<PartialMerkleTree>("partialMerkleTree", JsonNode::isObject, mapper, parser)
|
||||
|
||||
return TransactionSignature(bytes, by, signatureMetadata, partialMerkleTree)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object SignedTransactionSerde : JsonSerde<SignedTransaction> {
|
||||
override val type: Class<SignedTransaction> = SignedTransaction::class.java
|
||||
@ToStringSerialize
|
||||
private interface X500PrincipalMixin
|
||||
|
||||
override val serializer = object : StdSerializer<SignedTransaction>(type) {
|
||||
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.jsonObject {
|
||||
writeObjectField("txBits", value.txBits.bytes)
|
||||
writeObjectField("signatures", value.sigs)
|
||||
@JsonSerialize(using = X509CertificateSerializer::class)
|
||||
@JsonDeserialize(using = X509CertificateDeserializer::class)
|
||||
private interface X509CertificateMixin
|
||||
|
||||
private object X509CertificateSerializer : JsonSerializer<X509Certificate>() {
|
||||
val keyUsages = arrayOf(
|
||||
"digitalSignature",
|
||||
"nonRepudiation",
|
||||
"keyEncipherment",
|
||||
"dataEncipherment",
|
||||
"keyAgreement",
|
||||
"keyCertSign",
|
||||
"cRLSign",
|
||||
"encipherOnly",
|
||||
"decipherOnly"
|
||||
)
|
||||
|
||||
val keyPurposeIds = KeyPurposeId::class.java
|
||||
.fields
|
||||
.filter { Modifier.isStatic(it.modifiers) && it.type == KeyPurposeId::class.java }
|
||||
.associateBy({ (it.get(null) as KeyPurposeId).id }, { it.name })
|
||||
|
||||
val knownExtensions = setOf("2.5.29.15", "2.5.29.37", "2.5.29.19", "2.5.29.17", "2.5.29.18", CordaOID.X509_EXTENSION_CORDA_ROLE)
|
||||
|
||||
override fun serialize(value: X509Certificate, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.jsonObject {
|
||||
writeNumberField("version", value.version)
|
||||
writeObjectField("serialNumber", value.serialNumber)
|
||||
writeObjectField("subject", value.subjectX500Principal)
|
||||
writeObjectField("publicKey", value.publicKey)
|
||||
writeObjectField("issuer", value.issuerX500Principal)
|
||||
writeObjectField("notBefore", value.notBefore)
|
||||
writeObjectField("notAfter", value.notAfter)
|
||||
writeObjectField("issuerUniqueID", value.issuerUniqueID)
|
||||
writeObjectField("subjectUniqueID", value.subjectUniqueID)
|
||||
writeObjectField("keyUsage", value.keyUsage?.asList()?.mapIndexedNotNull { i, flag -> if (flag) keyUsages[i] else null })
|
||||
writeObjectField("extendedKeyUsage", value.extendedKeyUsage.map { keyPurposeIds.getOrDefault(it, it) })
|
||||
jsonObject("basicConstraints") {
|
||||
writeBooleanField("isCA", value.basicConstraints != -1)
|
||||
writeObjectField("pathLength", value.basicConstraints.let { if (it != Int.MAX_VALUE) it else null })
|
||||
}
|
||||
writeObjectField("subjectAlternativeNames", value.subjectAlternativeNames)
|
||||
writeObjectField("issuerAlternativeNames", value.issuerAlternativeNames)
|
||||
writeObjectField("cordaCertRole", CertRole.extract(value))
|
||||
writeObjectField("otherCriticalExtensions", value.criticalExtensionOIDs - knownExtensions)
|
||||
writeObjectField("otherNonCriticalExtensions", value.nonCriticalExtensionOIDs - knownExtensions)
|
||||
writeBinaryField("encoded", value.encoded)
|
||||
}
|
||||
}
|
||||
|
||||
override val deserializer = object : StdDeserializer<SignedTransaction>(type) {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): SignedTransaction {
|
||||
val mapper = parser.codec as ObjectMapper
|
||||
val json = mapper.readTree<JsonNode>(parser)
|
||||
|
||||
val txBits = json.get<ByteArray>("txBits", JsonNode::isTextual, mapper, parser)
|
||||
val signatures = json.get<TransactionSignatures>("signatures", JsonNode::isArray, mapper, parser)
|
||||
|
||||
return SignedTransaction(SerializedBytes(txBits), signatures)
|
||||
}
|
||||
}
|
||||
|
||||
private class TransactionSignatures : ArrayList<TransactionSignature>()
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// The following should not have been made public and are thus deprecated with warnings.
|
||||
//
|
||||
|
||||
@Deprecated("No longer used as jackson already has a toString serializer",
|
||||
replaceWith = ReplaceWith("com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance"))
|
||||
object ToStringSerializer : JsonSerializer<Any>() {
|
||||
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.toString())
|
||||
private class X509CertificateDeserializer : JsonDeserializer<X509Certificate>() {
|
||||
private val certFactory = CertificateFactory.getInstance("X.509")
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): X509Certificate {
|
||||
val encoded = parser.readValueAsTree<ObjectNode>()["encoded"]
|
||||
return certFactory.generateCertificate(encoded.binaryValue().inputStream()) as X509Certificate
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = CertPathSerializer::class)
|
||||
@JsonDeserialize(using = CertPathDeserializer::class)
|
||||
private interface CertPathMixin
|
||||
|
||||
private class CertPathSerializer : JsonSerializer<CertPath>() {
|
||||
override fun serialize(value: CertPath, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeObject(CertPathWrapper(value.type, uncheckedCast(value.certificates)))
|
||||
}
|
||||
}
|
||||
|
||||
private class CertPathDeserializer : JsonDeserializer<CertPath>() {
|
||||
private val certFactory = CertificateFactory.getInstance("X.509")
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CertPath {
|
||||
val wrapper = parser.readValueAs<CertPathWrapper>()
|
||||
return certFactory.generateCertPath(wrapper.certificates)
|
||||
}
|
||||
}
|
||||
|
||||
private data class CertPathWrapper(val type: String, val certificates: List<X509Certificate>) {
|
||||
init {
|
||||
require(type == "X.509") { "Only X.509 cert paths are supported" }
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
@ -310,6 +369,9 @@ 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) {
|
||||
@ -344,13 +406,9 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
// This is no longer used
|
||||
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() {
|
||||
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.toString())
|
||||
}
|
||||
}
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = CordaX500NameDeserializer::class)
|
||||
private interface CordaX500NameMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
|
||||
@ -363,13 +421,9 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
// This is no longer used
|
||||
object NodeInfoSerializer : JsonSerializer<NodeInfo>() {
|
||||
override fun serialize(value: NodeInfo, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(Base58.encode(value.serialize().bytes))
|
||||
}
|
||||
}
|
||||
@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>() {
|
||||
@ -380,17 +434,10 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
// This is no longer used
|
||||
object SecureHashSerializer : JsonSerializer<SecureHash>() {
|
||||
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.toString())
|
||||
}
|
||||
}
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = SecureHashDeserializer::class)
|
||||
private interface SecureHashSHA256Mixin
|
||||
|
||||
/**
|
||||
* Implemented as a class so that we can instantiate for T.
|
||||
*/
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
|
||||
@ -402,6 +449,10 @@ 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) {
|
||||
@ -420,13 +471,9 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
// This is no longer used
|
||||
object AmountSerializer : JsonSerializer<Amount<*>>() {
|
||||
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(value.toString())
|
||||
}
|
||||
}
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = AmountDeserializer::class)
|
||||
private interface AmountMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
|
||||
@ -434,20 +481,30 @@ object JacksonSupport {
|
||||
return if (parser.currentToken == JsonToken.VALUE_STRING) {
|
||||
Amount.parseCurrency(parser.text)
|
||||
} else {
|
||||
try {
|
||||
val tree = parser.readValueAsTree<ObjectNode>()
|
||||
val quantity = tree["quantity"].apply { require(canConvertToLong()) }
|
||||
val token = tree["token"]
|
||||
// Attempt parsing as a currency token. TODO: This needs thought about how to extend to other token types.
|
||||
val currency = (parser.codec as ObjectMapper).convertValue<Currency>(token)
|
||||
Amount(quantity.longValue(), currency)
|
||||
} catch (e: Exception) {
|
||||
throw JsonParseException(parser, "Invalid amount", e)
|
||||
}
|
||||
val wrapper = parser.readValueAs<CurrencyAmountWrapper>()
|
||||
Amount(wrapper.quantity, wrapper.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -455,6 +512,47 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Everything below this point is no longer used but can't be deleted as they leaked into the public API
|
||||
//
|
||||
|
||||
@Deprecated("No longer used as jackson already has a toString serializer",
|
||||
replaceWith = ReplaceWith("com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance"))
|
||||
object ToStringSerializer : JsonSerializer<Any>() {
|
||||
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() {
|
||||
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object NodeInfoSerializer : JsonSerializer<NodeInfo>() {
|
||||
override fun serialize(value: NodeInfo, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(Base58.encode(value.serialize().bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object SecureHashSerializer : JsonSerializer<SecureHash>() {
|
||||
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object AmountSerializer : JsonSerializer<Amount<*>>() {
|
||||
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() {
|
||||
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
@ -467,7 +565,7 @@ object JacksonSupport {
|
||||
abstract class SignedTransactionMixin {
|
||||
@JsonIgnore abstract fun getTxBits(): SerializedBytes<CoreTransaction>
|
||||
@JsonProperty("signatures") protected abstract fun getSigs(): List<TransactionSignature>
|
||||
@JsonProperty protected abstract fun getTransaction(): CoreTransaction // TODO It seems this should be coreTransaction
|
||||
@JsonProperty protected abstract fun getTransaction(): CoreTransaction
|
||||
@JsonIgnore abstract fun getTx(): WireTransaction
|
||||
@JsonIgnore abstract fun getNotaryChangeTx(): NotaryChangeWireTransaction
|
||||
@JsonIgnore abstract fun getInputs(): List<StateRef>
|
||||
|
@ -3,8 +3,11 @@ package net.corda.client.jackson.internal
|
||||
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.module.kotlin.convertValue
|
||||
|
||||
inline fun <reified T : Any> SimpleModule.addSerAndDeser(serializer: JsonSerializer<in T>, deserializer: JsonDeserializer<T>) {
|
||||
addSerializer(T::class.java, serializer)
|
||||
@ -19,3 +22,5 @@ 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)
|
||||
|
@ -1,13 +1,15 @@
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.fasterxml.jackson.core.JsonFactory
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.node.BinaryNode
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.databind.node.TextNode
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import com.fasterxml.jackson.module.kotlin.convertValue
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.client.jackson.internal.valueAs
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.*
|
||||
@ -16,14 +18,16 @@ 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.DigitalSignatureWithCert
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.utilities.toBase64
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.finance.USD
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.*
|
||||
@ -34,19 +38,29 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.junit.runners.Parameterized.Parameters
|
||||
import java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class JacksonSupportTest {
|
||||
@RunWith(Parameterized::class)
|
||||
class JacksonSupportTest(@Suppress("unused") private val name: String, factory: JsonFactory) {
|
||||
private companion object {
|
||||
val SEED: BigInteger = BigInteger.valueOf(20170922L)
|
||||
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
|
||||
val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun factories() = arrayOf(arrayOf("JSON", JsonFactory()), arrayOf("YAML", YAMLFactory()))
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -54,7 +68,7 @@ class JacksonSupportTest {
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private val partyObjectMapper = TestPartyObjectMapper()
|
||||
private val mapper = JacksonSupport.createPartyObjectMapper(partyObjectMapper)
|
||||
private val mapper = JacksonSupport.createPartyObjectMapper(partyObjectMapper, factory)
|
||||
|
||||
private lateinit var services: ServiceHub
|
||||
private lateinit var cordappProvider: CordappProvider
|
||||
@ -66,44 +80,29 @@ class JacksonSupportTest {
|
||||
doReturn(cordappProvider).whenever(services).cordappProvider
|
||||
}
|
||||
|
||||
private class Dummy(val notional: Amount<Currency>)
|
||||
|
||||
@Test
|
||||
fun `read Amount`() {
|
||||
val oldJson = """
|
||||
{
|
||||
"notional": {
|
||||
"quantity": 2500000000,
|
||||
"token": "USD"
|
||||
}
|
||||
}
|
||||
"""
|
||||
val newJson = """ { "notional" : "$25000000" } """
|
||||
|
||||
assertEquals(Amount(2500000000L, USD), mapper.readValue(newJson, Dummy::class.java).notional)
|
||||
assertEquals(Amount(2500000000L, USD), mapper.readValue(oldJson, Dummy::class.java).notional)
|
||||
fun `Amount(Currency) serialization`() {
|
||||
assertThat(mapper.valueToTree<TextNode>(Amount.parseCurrency("£25000000")).textValue()).isEqualTo("25000000.00 GBP")
|
||||
assertThat(mapper.valueToTree<TextNode>(Amount.parseCurrency("$250000")).textValue()).isEqualTo("250000.00 USD")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `write Amount`() {
|
||||
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT)
|
||||
assertEquals("""{"notional":"25000000.00 GBP"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("£25000000"))))
|
||||
assertEquals("""{"notional":"250000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$250000"))))
|
||||
fun `Amount(Currency) deserialization`() {
|
||||
val old = mapOf(
|
||||
"quantity" to 2500000000,
|
||||
"token" to "USD"
|
||||
)
|
||||
assertThat(mapper.convertValue<Amount<Currency>>(old)).isEqualTo(Amount(2_500_000_000, USD))
|
||||
assertThat(mapper.convertValue<Amount<Currency>>(TextNode("$25000000"))).isEqualTo(Amount(2_500_000_000, USD))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun SignedTransaction() {
|
||||
val attachmentRef = SecureHash.randomSHA256()
|
||||
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
|
||||
doReturn(testNetworkParameters()).whenever(services).networkParameters
|
||||
|
||||
val writer = mapper.writer()
|
||||
val stx = makeDummyStx()
|
||||
val json = writer.writeValueAsString(stx)
|
||||
|
||||
val deserializedTransaction = mapper.readValue(json, SignedTransaction::class.java)
|
||||
|
||||
assertThat(deserializedTransaction).isEqualTo(stx)
|
||||
fun ByteSequence() {
|
||||
val byteSequence: ByteSequence = OpaqueBytes.of(1, 2, 3, 4).subSequence(0, 2)
|
||||
val json = mapper.valueToTree<BinaryNode>(byteSequence)
|
||||
assertThat(json.binaryValue()).containsExactly(1, 2)
|
||||
assertThat(json.asText()).isEqualTo(byteArrayOf(1, 2).toBase64())
|
||||
assertThat(mapper.convertValue<ByteSequence>(json)).isEqualTo(byteSequence)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -115,6 +114,105 @@ class JacksonSupportTest {
|
||||
assertThat(mapper.convertValue<OpaqueBytes>(json)).isEqualTo(opaqueBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun SerializedBytes() {
|
||||
val data = TestData(BOB_NAME, "Summary", SubTestData(1234))
|
||||
val serializedBytes = data.serialize()
|
||||
val json = mapper.valueToTree<ObjectNode>(serializedBytes)
|
||||
println(mapper.writeValueAsString(json))
|
||||
assertThat(json["class"].textValue()).isEqualTo(TestData::class.java.name)
|
||||
assertThat(json["deserialized"].valueAs<TestData>(mapper)).isEqualTo(data)
|
||||
// Check that the entire JSON object can be converted back to the same SerializedBytes
|
||||
assertThat(mapper.convertValue<SerializedBytes<*>>(json)).isEqualTo(serializedBytes)
|
||||
assertThat(mapper.convertValue<SerializedBytes<*>>(BinaryNode(serializedBytes.bytes))).isEqualTo(serializedBytes)
|
||||
}
|
||||
|
||||
// This is the class that was used to serialise the message for the test below. It's commented out so that it's no
|
||||
// longer on the classpath.
|
||||
// @CordaSerializable
|
||||
// data class ClassNotOnClasspath(val name: CordaX500Name, val value: Int)
|
||||
|
||||
@Test
|
||||
fun `SerializedBytes of class not on classpath`() {
|
||||
// The contents of the file were written out as follows:
|
||||
// ClassNotOnClasspath(BOB_NAME, 54321).serialize().open().copyTo("build" / "class-not-on-classpath-data")
|
||||
|
||||
val serializedBytes = SerializedBytes<Any>(javaClass.getResource("class-not-on-classpath-data").readBytes())
|
||||
val json = mapper.valueToTree<ObjectNode>(serializedBytes)
|
||||
println(mapper.writeValueAsString(json))
|
||||
assertThat(json["class"].textValue()).isEqualTo("net.corda.client.jackson.JacksonSupportTest\$ClassNotOnClasspath")
|
||||
assertThat(json["deserialized"].valueAs<Map<*, *>>(mapper)).isEqualTo(mapOf(
|
||||
"name" to BOB_NAME.toString(),
|
||||
"value" to 54321
|
||||
))
|
||||
assertThat(mapper.convertValue<SerializedBytes<*>>(BinaryNode(serializedBytes.bytes))).isEqualTo(serializedBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun DigitalSignature() {
|
||||
val digitalSignature = DigitalSignature(secureRandomBytes(128))
|
||||
val json = mapper.valueToTree<BinaryNode>(digitalSignature)
|
||||
assertThat(json.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||
assertThat(json.asText()).isEqualTo(digitalSignature.bytes.toBase64())
|
||||
assertThat(mapper.convertValue<DigitalSignature>(json)).isEqualTo(digitalSignature)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DigitalSignature WithKey`() {
|
||||
val digitalSignature = DigitalSignature.WithKey(BOB_PUBKEY, secureRandomBytes(128))
|
||||
val json = mapper.valueToTree<ObjectNode>(digitalSignature)
|
||||
val (by, bytes) = json.assertHasOnlyFields("by", "bytes")
|
||||
assertThat(by.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
|
||||
assertThat(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||
assertThat(mapper.convertValue<DigitalSignature.WithKey>(json)).isEqualTo(digitalSignature)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun DigitalSignatureWithCert() {
|
||||
val digitalSignature = DigitalSignatureWithCert(MINI_CORP.identity.certificate, secureRandomBytes(128))
|
||||
val json = mapper.valueToTree<ObjectNode>(digitalSignature)
|
||||
val (by, bytes) = json.assertHasOnlyFields("by", "bytes")
|
||||
assertThat(by.valueAs<X509Certificate>(mapper)).isEqualTo(MINI_CORP.identity.certificate)
|
||||
assertThat(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||
assertThat(mapper.convertValue<DigitalSignatureWithCert>(json)).isEqualTo(digitalSignature)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun TransactionSignature() {
|
||||
val metadata = SignatureMetadata(1, 1)
|
||||
val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, metadata)
|
||||
val json = mapper.valueToTree<ObjectNode>(transactionSignature)
|
||||
val (bytes, by, signatureMetadata, partialMerkleTree) = json.assertHasOnlyFields(
|
||||
"bytes",
|
||||
"by",
|
||||
"signatureMetadata",
|
||||
"partialMerkleTree"
|
||||
)
|
||||
assertThat(bytes.binaryValue()).isEqualTo(transactionSignature.bytes)
|
||||
assertThat(by.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
|
||||
assertThat(signatureMetadata.valueAs<SignatureMetadata>(mapper)).isEqualTo(metadata)
|
||||
assertThat(partialMerkleTree.isNull).isTrue()
|
||||
assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature)
|
||||
}
|
||||
|
||||
// TODO Add test for PartialMerkleTree
|
||||
|
||||
@Test
|
||||
fun SignedTransaction() {
|
||||
val attachmentRef = SecureHash.randomSHA256()
|
||||
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
|
||||
doReturn(testNetworkParameters()).whenever(services).networkParameters
|
||||
|
||||
val stx = makeDummyStx()
|
||||
val json = mapper.valueToTree<ObjectNode>(stx)
|
||||
println(mapper.writeValueAsString(json))
|
||||
val (txBits, signatures) = json.assertHasOnlyFields("txBits", "signatures")
|
||||
assertThat(txBits.binaryValue()).isEqualTo(stx.txBits.bytes)
|
||||
val sigs = signatures.elements().asSequence().map { it.valueAs<TransactionSignature>(mapper) }.toList()
|
||||
assertThat(sigs).isEqualTo(stx.sigs)
|
||||
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun CordaX500Name() {
|
||||
testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US"))
|
||||
@ -211,31 +309,40 @@ class JacksonSupportTest {
|
||||
|
||||
@Test
|
||||
fun AnonymousParty() {
|
||||
val anon = AnonymousParty(ALICE_PUBKEY)
|
||||
val json = mapper.valueToTree<TextNode>(anon)
|
||||
val anonymousParty = AnonymousParty(ALICE_PUBKEY)
|
||||
val json = mapper.valueToTree<TextNode>(anonymousParty)
|
||||
assertThat(json.textValue()).isEqualTo(ALICE_PUBKEY.toBase58String())
|
||||
assertThat(mapper.convertValue<AnonymousParty>(json)).isEqualTo(anon)
|
||||
assertThat(mapper.convertValue<AnonymousParty>(json)).isEqualTo(anonymousParty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PartyAndCertificate serialisation`() {
|
||||
val json = mapper.valueToTree<ObjectNode>(MINI_CORP.identity)
|
||||
assertThat(json.fieldNames()).containsOnly("name", "owningKey")
|
||||
assertThat(mapper.convertValue<CordaX500Name>(json["name"])).isEqualTo(MINI_CORP.name)
|
||||
assertThat(mapper.convertValue<PublicKey>(json["owningKey"])).isEqualTo(MINI_CORP.publicKey)
|
||||
val (name, owningKey) = json.assertHasOnlyFields("name", "owningKey")
|
||||
assertThat(name.valueAs<CordaX500Name>(mapper)).isEqualTo(MINI_CORP.name)
|
||||
assertThat(owningKey.valueAs<PublicKey>(mapper)).isEqualTo(MINI_CORP.publicKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NodeInfo serialisation`() {
|
||||
val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
|
||||
val json = mapper.valueToTree<ObjectNode>(nodeInfo)
|
||||
assertThat(json.fieldNames()).containsOnly("addresses", "legalIdentitiesAndCerts", "platformVersion", "serial")
|
||||
val address = (json["addresses"] as ArrayNode).also { assertThat(it).hasSize(1) }[0]
|
||||
assertThat(mapper.convertValue<NetworkHostAndPort>(address)).isEqualTo(nodeInfo.addresses[0])
|
||||
val identity = (json["legalIdentitiesAndCerts"] as ArrayNode).also { assertThat(it).hasSize(1) }[0]
|
||||
assertThat(mapper.convertValue<CordaX500Name>(identity["name"])).isEqualTo(ALICE_NAME)
|
||||
assertThat(mapper.convertValue<Int>(json["platformVersion"])).isEqualTo(nodeInfo.platformVersion)
|
||||
assertThat(mapper.convertValue<Long>(json["serial"])).isEqualTo(nodeInfo.serial)
|
||||
val (addresses, legalIdentitiesAndCerts, platformVersion, serial) = json.assertHasOnlyFields(
|
||||
"addresses",
|
||||
"legalIdentitiesAndCerts",
|
||||
"platformVersion",
|
||||
"serial"
|
||||
)
|
||||
addresses.run {
|
||||
assertThat(this).hasSize(1)
|
||||
assertThat(this[0].valueAs<NetworkHostAndPort>(mapper)).isEqualTo(nodeInfo.addresses[0])
|
||||
}
|
||||
legalIdentitiesAndCerts.run {
|
||||
assertThat(this).hasSize(1)
|
||||
assertThat(this[0]["name"].valueAs<CordaX500Name>(mapper)).isEqualTo(ALICE_NAME)
|
||||
}
|
||||
assertThat(platformVersion.intValue()).isEqualTo(nodeInfo.platformVersion)
|
||||
assertThat(serial.longValue()).isEqualTo(nodeInfo.serial)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -264,6 +371,40 @@ class JacksonSupportTest {
|
||||
assertThat(convertToNodeInfo()).isEqualTo(nodeInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun CertPath() {
|
||||
val certPath = MINI_CORP.identity.certPath
|
||||
val json = mapper.valueToTree<ObjectNode>(certPath)
|
||||
println(mapper.writeValueAsString(json))
|
||||
val (type, certificates) = json.assertHasOnlyFields("type", "certificates")
|
||||
assertThat(type.textValue()).isEqualTo(certPath.type)
|
||||
certificates.run {
|
||||
val serialNumbers = elements().asSequence().map { it["serialNumber"].bigIntegerValue() }.toList()
|
||||
assertThat(serialNumbers).isEqualTo(certPath.x509Certificates.map { it.serialNumber })
|
||||
}
|
||||
assertThat(mapper.convertValue<CertPath>(json).encoded).isEqualTo(certPath.encoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun X509Certificate() {
|
||||
val cert: X509Certificate = MINI_CORP.identity.certificate
|
||||
val json = mapper.valueToTree<ObjectNode>(cert)
|
||||
println(mapper.writeValueAsString(json))
|
||||
assertThat(json["serialNumber"].bigIntegerValue()).isEqualTo(cert.serialNumber)
|
||||
assertThat(json["issuer"].valueAs<X500Principal>(mapper)).isEqualTo(cert.issuerX500Principal)
|
||||
assertThat(json["subject"].valueAs<X500Principal>(mapper)).isEqualTo(cert.subjectX500Principal)
|
||||
assertThat(json["publicKey"].valueAs<PublicKey>(mapper)).isEqualTo(cert.publicKey)
|
||||
assertThat(json["notAfter"].valueAs<Date>(mapper)).isEqualTo(cert.notAfter)
|
||||
assertThat(json["notBefore"].valueAs<Date>(mapper)).isEqualTo(cert.notBefore)
|
||||
assertThat(json["encoded"].binaryValue()).isEqualTo(cert.encoded)
|
||||
assertThat(mapper.convertValue<X509Certificate>(json).encoded).isEqualTo(cert.encoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun X500Principal() {
|
||||
testToStringSerialisation(X500Principal("CN=Common,L=London,O=Org,C=UK"))
|
||||
}
|
||||
|
||||
private fun makeDummyStx(): SignedTransaction {
|
||||
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
|
||||
.toWireTransaction(services)
|
||||
@ -280,6 +421,17 @@ class JacksonSupportTest {
|
||||
assertThat(mapper.convertValue<T>(json)).isEqualTo(value)
|
||||
}
|
||||
|
||||
private fun JsonNode.assertHasOnlyFields(vararg fieldNames: String): List<JsonNode> {
|
||||
assertThat(fieldNames()).containsOnly(*fieldNames)
|
||||
return fieldNames.map { this[it] }
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
private data class TestData(val name: CordaX500Name, val summary: String, val subData: SubTestData)
|
||||
|
||||
@CordaSerializable
|
||||
private data class SubTestData(val value: Int)
|
||||
|
||||
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
|
||||
val identities = ArrayList<Party>()
|
||||
val nodes = ArrayList<NodeInfo>()
|
||||
|
Binary file not shown.
@ -22,7 +22,10 @@ Unreleased
|
||||
* ``NodeInfo`` objects are serialised as an object and can be looked up using the same mechanism as ``Party``
|
||||
* ``NetworkHostAndPort`` serialised according to its ``toString()``
|
||||
* ``PartyAndCertificate`` is serialised as an object containing the name and owning key
|
||||
* ``SignedTransaction`` can now be serialized to JSON and deserialized back into an object.
|
||||
* ``SerializedBytes`` is serialised by converting the bytes into the object it represents, which is then serialised into
|
||||
a JSON/YAML object
|
||||
* ``CertPath`` and ``X509Certificate`` are serialised as objects and can be deserialised back
|
||||
* ``SignedTransaction`` is serialised into its ``txBits`` and ``signatures`` and can be deserialised back
|
||||
|
||||
* Several members of ``JacksonSupport`` have been deprecated to highlight that they are internal and not to be used.
|
||||
|
||||
|
@ -267,8 +267,8 @@ SecureHash
|
||||
~~~~~~~~~~
|
||||
A parameter of type ``SecureHash`` can be written as a hexadecimal string: ``F69A7626ACC27042FEEAE187E6BFF4CE666E6F318DC2B32BE9FAF87DF687930C``
|
||||
|
||||
OpaqueBytes
|
||||
~~~~~~~~~~~
|
||||
OpaqueBytes and SerializedBytes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
A parameter of type ``OpaqueBytes`` can be provided as a string in Base64.
|
||||
|
||||
PublicKey and CompositeKey
|
||||
|
Loading…
Reference in New Issue
Block a user