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:
Shams Asari 2018-05-15 17:02:43 +01:00 committed by GitHub
parent 7da0bcd741
commit b031e66ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 582 additions and 207 deletions

117
.idea/compiler.xml generated
View File

@ -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">

View File

@ -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>

View File

@ -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)

View File

@ -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>()

View File

@ -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.

View File

@ -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