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"> <bytecodeTargetLevel target="1.8">
<module name="api-scanner_main" target="1.8" /> <module name="api-scanner_main" target="1.8" />
<module name="api-scanner_test" 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_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_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_integrationTest" target="1.8" />
<module name="corda-core_smokeTest" target="1.8" /> <module name="corda-core_smokeTest" target="1.8" />
<module name="corda-finance_integrationTest" 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_integrationTest" target="1.8" />
<module name="corda-webserver_main" target="1.8" /> <module name="corda-webserver_main" target="1.8" />
<module name="corda-webserver_test" 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_main" target="1.8" />
<module name="cordform-common_test" target="1.8" /> <module name="cordform-common_test" target="1.8" />
<module name="cordformation_main" target="1.8" /> <module name="cordformation_main" target="1.8" />
<module name="cordformation_runnodes" target="1.8" /> <module name="cordformation_runnodes" target="1.8" />
<module name="cordformation_test" target="1.8" /> <module name="cordformation_test" target="1.8" />
<module name="core_extraResource" 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_integrationTest" target="1.8" />
<module name="docs_source_example-code_main" 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_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_main" target="1.8" />
<module name="experimental-kryo-hook_test" 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_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_main" target="1.8" />
<module name="gradle-plugins-cordapp_test" 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_integrationTest" target="1.8" />
<module name="irs-demo-cordapp_main" target="1.8" /> <module name="irs-demo-cordapp_main" target="1.8" />
<module name="irs-demo-cordapp_main~1" 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-cordapp_test~1" target="1.8" />
<module name="irs-demo-web_main" 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-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_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_main" target="1.8" />
<module name="publish-utils_test" 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_main" target="1.8" />
<module name="quasar-utils_test" 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_integrationTest" target="1.8" />
<module name="source-example-code_main" target="1.8" /> <module name="source-example-code_main" target="1.8" />
<module name="source-example-code_test" 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_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_integrationTest" target="1.8" />
<module name="testing-node-driver_main" target="1.8" /> <module name="testing-node-driver_main" target="1.8" />
<module name="testing-node-driver_test" 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-common_test" target="1.8" />
<module name="testing-test-utils_main" target="1.8" /> <module name="testing-test-utils_main" target="1.8" />
<module name="testing-test-utils_test" 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_integrationTest" target="1.8" />
<module name="verifier_main" target="1.8" /> <module name="verifier_main" target="1.8" />
<module name="verifier_test" 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_main" target="1.8" />
<module name="webserver-webcapsule_test" 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> </bytecodeTargetLevel>
</component> </component>
<component name="JavacSettings"> <component name="JavacSettings">

View File

@ -1,21 +1,20 @@
package net.corda.client.jackson package net.corda.client.jackson
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.* import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers 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.module.SimpleModule
import com.fasterxml.jackson.databind.node.ObjectNode 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.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.convertValue import 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.jsonObject
import net.corda.client.jackson.internal.readValueAs import net.corda.client.jackson.internal.readValueAs
import net.corda.core.CordaInternal import net.corda.core.CordaInternal
import net.corda.core.CordaOID
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState 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.*
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.* 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.VisibleForTesting
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.*
import net.corda.core.utilities.OpaqueBytes import org.bouncycastle.asn1.x509.KeyPurposeId
import net.corda.core.utilities.parsePublicKeyBase58 import java.lang.reflect.Modifier
import net.corda.core.utilities.toBase58String
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.* 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 * 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 { val cordaModule: Module by lazy {
SimpleModule("core").apply { SimpleModule("core").apply {
addSerAndDeser(AnonymousPartySerializer, AnonymousPartyDeserializer) setMixInAnnotation(BigDecimal::class.java, BigDecimalMixin::class.java)
addSerAndDeser(PartySerializer, PartyDeserializer) setMixInAnnotation(X500Principal::class.java, X500PrincipalMixin::class.java)
addDeserializer(AbstractParty::class.java, PartyDeserializer) setMixInAnnotation(X509Certificate::class.java, X509CertificateMixin::class.java)
addSerAndDeser<BigDecimal>(toStringSerializer, NumberDeserializers.BigDecimalDeserializer()) setMixInAnnotation(PartyAndCertificate::class.java, PartyAndCertificateSerializerMixin::class.java)
addSerAndDeser<SecureHash.SHA256>(toStringSerializer, SecureHashDeserializer()) setMixInAnnotation(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java)
addSerAndDeser(toStringSerializer, AmountDeserializer) setMixInAnnotation(CordaX500Name::class.java, CordaX500NameMixin::class.java)
addSerAndDeser(OpaqueBytesSerializer, OpaqueBytesDeserializer) setMixInAnnotation(Amount::class.java, AmountMixin::class.java)
addSerAndDeser(toStringSerializer, CordaX500NameDeserializer) setMixInAnnotation(AbstractParty::class.java, AbstractPartyMixin::class.java)
addSerAndDeser(PublicKeySerializer, PublicKeyDeserializer) setMixInAnnotation(AnonymousParty::class.java, AnonymousPartyMixin::class.java)
addDeserializer(CompositeKey::class.java, CompositeKeyDeseriaizer) setMixInAnnotation(Party::class.java, PartyMixin::class.java)
addSerAndDeser(toStringSerializer, NetworkHostAndPortDeserializer) setMixInAnnotation(PublicKey::class.java, PublicKeyMixin::class.java)
// TODO Add deserialization which follows the same lookup logic as Party setMixInAnnotation(ByteSequence::class.java, ByteSequenceMixin::class.java)
addSerializer(PartyAndCertificate::class.java, PartyAndCertificateSerializer) setMixInAnnotation(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
addDeserializer(NodeInfo::class.java, NodeInfoDeserializer) setMixInAnnotation(SerializedBytes::class.java, SerializedBytesMixin::class.java)
setMixInAnnotation(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
listOf(TransactionSignatureSerde, SignedTransactionSerde).forEach { serde -> serde.applyTo(this) } setMixInAnnotation(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
setMixInAnnotation(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
// Using mixins to fine-tune the default serialised output setMixInAnnotation(SignedTransaction::class.java, SignedTransactionMixin2::class.java)
setMixInAnnotation(WireTransaction::class.java, WireTransactionMixin::class.java) setMixInAnnotation(WireTransaction::class.java, WireTransactionMixin::class.java)
setMixInAnnotation(CertPath::class.java, CertPathMixin::class.java)
setMixInAnnotation(NodeInfo::class.java, NodeInfoMixin::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>() { private object DateSerializer : JsonSerializer<Date>() {
override fun serialize(value: Date, gen: JsonGenerator, serializers: SerializerProvider) { 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 { override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text) return NetworkHostAndPort.parse(parser.text)
} }
} }
private object CompositeKeyDeseriaizer : JsonDeserializer<CompositeKey>() { @JsonSerialize(using = PartyAndCertificateSerializer::class)
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CompositeKey { // TODO Add deserialization which follows the same lookup logic as Party
val publicKey = parser.readValueAs<PublicKey>() private interface PartyAndCertificateSerializerMixin
return publicKey as? CompositeKey ?: throw JsonParseException(parser, "Not a CompositeKey: $publicKey")
}
}
private object PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() { private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) { override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject { gen.jsonObject {
writeObjectField("name", value.name) writeObjectField("name", value.name)
@ -202,99 +215,145 @@ object JacksonSupport {
} }
} }
@Suppress("unused") @JsonSerialize(using = SignedTransactionSerializer::class)
private interface NodeInfoMixin { @JsonDeserialize(using = SignedTransactionDeserializer::class)
@get:JsonIgnore val legalIdentities: Any // This is already covered by legalIdentitiesAndCerts private interface SignedTransactionMixin2
}
private interface JsonSerde<TYPE> { private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
val type: Class<TYPE>
val serializer: JsonSerializer<TYPE>
val deserializer: JsonDeserializer<TYPE>
fun applyTo(module: SimpleModule) {
with(module) {
addSerializer(type, serializer)
addDeserializer(type, deserializer)
}
}
}
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 {
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)
}
}
}
private object SignedTransactionSerde : JsonSerde<SignedTransaction> {
override val type: Class<SignedTransaction> = SignedTransaction::class.java
override val serializer = object : StdSerializer<SignedTransaction>(type) {
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) { override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
}
}
private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
}
}
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
@JsonSerialize(using = SerializedBytesSerializer::class)
@JsonDeserialize(using = SerializedBytesDeserializer::class)
private class SerializedBytesMixin
private class SerializedBytesSerializer : JsonSerializer<SerializedBytes<*>>() {
override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
val deserialized = value.deserialize<Any>()
gen.jsonObject { gen.jsonObject {
writeObjectField("txBits", value.txBits.bytes) writeStringField("class", deserialized.javaClass.name)
writeObjectField("signatures", value.sigs) writeObjectField("deserialized", deserialized)
} }
} }
} }
override val deserializer = object : StdDeserializer<SignedTransaction>(type) { private class SerializedBytesDeserializer : JsonDeserializer<SerializedBytes<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): SignedTransaction { override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes<Any> {
return if (parser.currentToken == JsonToken.START_OBJECT) {
val mapper = parser.codec as ObjectMapper val mapper = parser.codec as ObjectMapper
val json = mapper.readTree<JsonNode>(parser) val json = parser.readValueAsTree<ObjectNode>()
val clazz = context.findClass(json["class"].textValue())
val txBits = json.get<ByteArray>("txBits", JsonNode::isTextual, mapper, parser) val pojo = mapper.convertValue(json["deserialized"], clazz)
val signatures = json.get<TransactionSignatures>("signatures", JsonNode::isArray, mapper, parser) pojo.serialize()
} else {
return SignedTransaction(SerializedBytes(txBits), signatures) SerializedBytes(parser.binaryValue)
}
} }
} }
private class TransactionSignatures : ArrayList<TransactionSignature>() @ToStringSerialize
private interface X500PrincipalMixin
@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)
// The following should not have been made public and are thus deprecated with warnings. writeObjectField("otherNonCriticalExtensions", value.nonCriticalExtensionOIDs - knownExtensions)
// writeBinaryField("encoded", value.encoded)
@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") @Deprecated("This is an internal class, do not use")
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() { object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
@ -310,6 +369,9 @@ object JacksonSupport {
} }
} }
@JsonSerialize(using = PartySerializer::class)
private interface PartyMixin
@Deprecated("This is an internal class, do not use") @Deprecated("This is an internal class, do not use")
object PartySerializer : JsonSerializer<Party>() { object PartySerializer : JsonSerializer<Party>() {
override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) { override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) {
@ -344,13 +406,9 @@ object JacksonSupport {
} }
} }
@Deprecated("This is an internal class, do not use") @ToStringSerialize
// This is no longer used @JsonDeserialize(using = CordaX500NameDeserializer::class)
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() { private interface CordaX500NameMixin
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
@Deprecated("This is an internal class, do not use") @Deprecated("This is an internal class, do not use")
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() { object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
@ -363,13 +421,9 @@ object JacksonSupport {
} }
} }
@Deprecated("This is an internal class, do not use") @JsonIgnoreProperties("legalIdentities") // This is already covered by legalIdentitiesAndCerts
// This is no longer used @JsonDeserialize(using = NodeInfoDeserializer::class)
object NodeInfoSerializer : JsonSerializer<NodeInfo>() { private interface NodeInfoMixin
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") @Deprecated("This is an internal class, do not use")
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() { object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
@ -380,17 +434,10 @@ object JacksonSupport {
} }
} }
@Deprecated("This is an internal class, do not use") @ToStringSerialize
// This is no longer used @JsonDeserialize(using = SecureHashDeserializer::class)
object SecureHashSerializer : JsonSerializer<SecureHash>() { private interface SecureHashSHA256Mixin
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
/**
* Implemented as a class so that we can instantiate for T.
*/
@Deprecated("This is an internal class, do not use") @Deprecated("This is an internal class, do not use")
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() { class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T { override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
@ -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") @Deprecated("This is an internal class, do not use")
object PublicKeySerializer : JsonSerializer<PublicKey>() { object PublicKeySerializer : JsonSerializer<PublicKey>() {
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) { override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
@ -420,13 +471,9 @@ object JacksonSupport {
} }
} }
@Deprecated("This is an internal class, do not use") @ToStringSerialize
// This is no longer used @JsonDeserialize(using = AmountDeserializer::class)
object AmountSerializer : JsonSerializer<Amount<*>>() { private interface AmountMixin
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toString())
}
}
@Deprecated("This is an internal class, do not use") @Deprecated("This is an internal class, do not use")
object AmountDeserializer : JsonDeserializer<Amount<*>>() { object AmountDeserializer : JsonDeserializer<Amount<*>>() {
@ -434,18 +481,28 @@ object JacksonSupport {
return if (parser.currentToken == JsonToken.VALUE_STRING) { return if (parser.currentToken == JsonToken.VALUE_STRING) {
Amount.parseCurrency(parser.text) Amount.parseCurrency(parser.text)
} else { } else {
try { val wrapper = parser.readValueAs<CurrencyAmountWrapper>()
val tree = parser.readValueAsTree<ObjectNode>() Amount(wrapper.quantity, wrapper.token)
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)
} }
} }
} }
private data class CurrencyAmountWrapper(val quantity: Long, val token: Currency)
@JsonDeserialize(using = OpaqueBytesDeserializer::class)
private interface ByteSequenceMixin {
@Suppress("unused")
@JsonValue
fun copyBytes(): ByteArray
}
@JsonIgnoreProperties("offset", "size")
@JsonSerialize
@JsonDeserialize
private interface ByteSequenceWithPropertiesMixin {
@Suppress("unused")
@JsonValue(false)
fun copyBytes(): ByteArray
} }
@Deprecated("This is an internal class, do not use") @Deprecated("This is an internal class, do not use")
@ -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") @Deprecated("This is an internal class, do not use")
object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() { object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() {
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) { override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
@ -467,7 +565,7 @@ object JacksonSupport {
abstract class SignedTransactionMixin { abstract class SignedTransactionMixin {
@JsonIgnore abstract fun getTxBits(): SerializedBytes<CoreTransaction> @JsonIgnore abstract fun getTxBits(): SerializedBytes<CoreTransaction>
@JsonProperty("signatures") protected abstract fun getSigs(): List<TransactionSignature> @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 getTx(): WireTransaction
@JsonIgnore abstract fun getNotaryChangeTx(): NotaryChangeWireTransaction @JsonIgnore abstract fun getNotaryChangeTx(): NotaryChangeWireTransaction
@JsonIgnore abstract fun getInputs(): List<StateRef> @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.JsonGenerator
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule 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>) { inline fun <reified T : Any> SimpleModule.addSerAndDeser(serializer: JsonSerializer<in T>, deserializer: JsonDeserializer<T>) {
addSerializer(T::class.java, serializer) 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> 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 package net.corda.client.jackson
import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.BinaryNode import com.fasterxml.jackson.databind.node.BinaryNode
import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue import com.fasterxml.jackson.module.kotlin.convertValue
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.jackson.internal.valueAs
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.* 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.AnonymousParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub 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.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String
import net.corda.core.utilities.toBase64
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.* import net.corda.testing.core.*
@ -34,19 +38,29 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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.math.BigInteger
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.* import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.collections.ArrayList 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 { private companion object {
val SEED: BigInteger = BigInteger.valueOf(20170922L) val SEED: BigInteger = BigInteger.valueOf(20170922L)
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
@Parameters(name = "{0}")
@JvmStatic
fun factories() = arrayOf(arrayOf("JSON", JsonFactory()), arrayOf("YAML", YAMLFactory()))
} }
@Rule @Rule
@ -54,7 +68,7 @@ class JacksonSupportTest {
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val partyObjectMapper = TestPartyObjectMapper() 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 services: ServiceHub
private lateinit var cordappProvider: CordappProvider private lateinit var cordappProvider: CordappProvider
@ -66,44 +80,29 @@ class JacksonSupportTest {
doReturn(cordappProvider).whenever(services).cordappProvider doReturn(cordappProvider).whenever(services).cordappProvider
} }
private class Dummy(val notional: Amount<Currency>)
@Test @Test
fun `read Amount`() { fun `Amount(Currency) serialization`() {
val oldJson = """ assertThat(mapper.valueToTree<TextNode>(Amount.parseCurrency("£25000000")).textValue()).isEqualTo("25000000.00 GBP")
{ assertThat(mapper.valueToTree<TextNode>(Amount.parseCurrency("$250000")).textValue()).isEqualTo("250000.00 USD")
"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)
} }
@Test @Test
fun `write Amount`() { fun `Amount(Currency) deserialization`() {
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT) val old = mapOf(
assertEquals("""{"notional":"25000000.00 GBP"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("£25000000")))) "quantity" to 2500000000,
assertEquals("""{"notional":"250000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$250000")))) "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 @Test
fun SignedTransaction() { fun ByteSequence() {
val attachmentRef = SecureHash.randomSHA256() val byteSequence: ByteSequence = OpaqueBytes.of(1, 2, 3, 4).subSequence(0, 2)
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) val json = mapper.valueToTree<BinaryNode>(byteSequence)
doReturn(testNetworkParameters()).whenever(services).networkParameters assertThat(json.binaryValue()).containsExactly(1, 2)
assertThat(json.asText()).isEqualTo(byteArrayOf(1, 2).toBase64())
val writer = mapper.writer() assertThat(mapper.convertValue<ByteSequence>(json)).isEqualTo(byteSequence)
val stx = makeDummyStx()
val json = writer.writeValueAsString(stx)
val deserializedTransaction = mapper.readValue(json, SignedTransaction::class.java)
assertThat(deserializedTransaction).isEqualTo(stx)
} }
@Test @Test
@ -115,6 +114,105 @@ class JacksonSupportTest {
assertThat(mapper.convertValue<OpaqueBytes>(json)).isEqualTo(opaqueBytes) 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 @Test
fun CordaX500Name() { fun CordaX500Name() {
testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US")) testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US"))
@ -211,31 +309,40 @@ class JacksonSupportTest {
@Test @Test
fun AnonymousParty() { fun AnonymousParty() {
val anon = AnonymousParty(ALICE_PUBKEY) val anonymousParty = AnonymousParty(ALICE_PUBKEY)
val json = mapper.valueToTree<TextNode>(anon) val json = mapper.valueToTree<TextNode>(anonymousParty)
assertThat(json.textValue()).isEqualTo(ALICE_PUBKEY.toBase58String()) assertThat(json.textValue()).isEqualTo(ALICE_PUBKEY.toBase58String())
assertThat(mapper.convertValue<AnonymousParty>(json)).isEqualTo(anon) assertThat(mapper.convertValue<AnonymousParty>(json)).isEqualTo(anonymousParty)
} }
@Test @Test
fun `PartyAndCertificate serialisation`() { fun `PartyAndCertificate serialisation`() {
val json = mapper.valueToTree<ObjectNode>(MINI_CORP.identity) val json = mapper.valueToTree<ObjectNode>(MINI_CORP.identity)
assertThat(json.fieldNames()).containsOnly("name", "owningKey") val (name, owningKey) = json.assertHasOnlyFields("name", "owningKey")
assertThat(mapper.convertValue<CordaX500Name>(json["name"])).isEqualTo(MINI_CORP.name) assertThat(name.valueAs<CordaX500Name>(mapper)).isEqualTo(MINI_CORP.name)
assertThat(mapper.convertValue<PublicKey>(json["owningKey"])).isEqualTo(MINI_CORP.publicKey) assertThat(owningKey.valueAs<PublicKey>(mapper)).isEqualTo(MINI_CORP.publicKey)
} }
@Test @Test
fun `NodeInfo serialisation`() { fun `NodeInfo serialisation`() {
val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME) val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
val json = mapper.valueToTree<ObjectNode>(nodeInfo) val json = mapper.valueToTree<ObjectNode>(nodeInfo)
assertThat(json.fieldNames()).containsOnly("addresses", "legalIdentitiesAndCerts", "platformVersion", "serial") val (addresses, legalIdentitiesAndCerts, platformVersion, serial) = json.assertHasOnlyFields(
val address = (json["addresses"] as ArrayNode).also { assertThat(it).hasSize(1) }[0] "addresses",
assertThat(mapper.convertValue<NetworkHostAndPort>(address)).isEqualTo(nodeInfo.addresses[0]) "legalIdentitiesAndCerts",
val identity = (json["legalIdentitiesAndCerts"] as ArrayNode).also { assertThat(it).hasSize(1) }[0] "platformVersion",
assertThat(mapper.convertValue<CordaX500Name>(identity["name"])).isEqualTo(ALICE_NAME) "serial"
assertThat(mapper.convertValue<Int>(json["platformVersion"])).isEqualTo(nodeInfo.platformVersion) )
assertThat(mapper.convertValue<Long>(json["serial"])).isEqualTo(nodeInfo.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 @Test
@ -264,6 +371,40 @@ class JacksonSupportTest {
assertThat(convertToNodeInfo()).isEqualTo(nodeInfo) 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 { private fun makeDummyStx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services) .toWireTransaction(services)
@ -280,6 +421,17 @@ class JacksonSupportTest {
assertThat(mapper.convertValue<T>(json)).isEqualTo(value) 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 { private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
val identities = ArrayList<Party>() val identities = ArrayList<Party>()
val nodes = ArrayList<NodeInfo>() 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`` * ``NodeInfo`` objects are serialised as an object and can be looked up using the same mechanism as ``Party``
* ``NetworkHostAndPort`` serialised according to its ``toString()`` * ``NetworkHostAndPort`` serialised according to its ``toString()``
* ``PartyAndCertificate`` is serialised as an object containing the name and owning key * ``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. * 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`` 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. A parameter of type ``OpaqueBytes`` can be provided as a string in Base64.
PublicKey and CompositeKey PublicKey and CompositeKey