diff --git a/.ci/README.md b/.ci/README.md
index 62b9669242..0f62099f17 100644
--- a/.ci/README.md
+++ b/.ci/README.md
@@ -1,7 +1,7 @@
# !! DO NOT MODIFY THE API FILE IN THIS DIRECTORY !!
The `api-current.txt` file contains a summary of Corda's current public APIs,
-as generated by the `api-scanner` Gradle plugin. (See [here](../gradle-plugins/api-scanner/README.md) for a detailed description of this plugin.) It will be regenerated and the copy in this repository updated by the Release Manager with
+as generated by the `api-scanner` Gradle plugin. (See [here](https://github.com/corda/corda-gradle-plugins/blob/master/api-scanner/README.md) for a detailed description of this plugin.) It will be regenerated and the copy in this repository updated by the Release Manager with
each new Corda release. It will not be modified otherwise except under special circumstances that will require extra approval.
Deleting or changing the existing Corda APIs listed in `api-current.txt` may
diff --git a/.gitignore b/.gitignore
index f82219eeff..b408dd575d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,7 +39,6 @@ lib/quasar.jar
.idea/runConfigurations
.idea/dictionaries
.idea/codeStyles/
-/gradle-plugins/.idea/
# Include the -parameters compiler option by default in IntelliJ required for serialization.
!.idea/compiler.xml
@@ -62,7 +61,6 @@ lib/quasar.jar
# Gradle:
# .idea/gradle.xml
# .idea/libraries
-/gradle-plugins/gradle*
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index f711367fa9..6891e6e828 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -10,40 +10,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -54,36 +32,34 @@
-
-
+
+
+
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
@@ -95,8 +71,6 @@
-
-
@@ -104,8 +78,13 @@
-
-
+
+
+
+
+
+
+
@@ -117,19 +96,12 @@
-
-
-
-
-
-
-
@@ -145,62 +117,45 @@
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
@@ -209,13 +164,12 @@
-
-
-
+
+
diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
index 57d4d3f3cf..536c46bdfd 100644
--- a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
@@ -10,22 +10,21 @@
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
@@ -33,24 +32,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
@@ -100,25 +105,26 @@ object JacksonSupport {
val cordaModule: Module by lazy {
SimpleModule("core").apply {
- addSerAndDeser(AnonymousPartySerializer, AnonymousPartyDeserializer)
- addSerAndDeser(PartySerializer, PartyDeserializer)
- addDeserializer(AbstractParty::class.java, PartyDeserializer)
- addSerAndDeser(toStringSerializer, NumberDeserializers.BigDecimalDeserializer())
- addSerAndDeser(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)
}
}
@@ -181,7 +187,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() {
override fun serialize(value: Date, gen: JsonGenerator, serializers: SerializerProvider) {
@@ -189,20 +201,21 @@ object JacksonSupport {
}
}
- private object NetworkHostAndPortDeserializer : JsonDeserializer() {
+ @ToStringSerialize
+ @JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
+ private interface NetworkHostAndPortMixin
+
+ private class NetworkHostAndPortDeserializer : JsonDeserializer() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text)
}
}
- private object CompositeKeyDeseriaizer : JsonDeserializer() {
- override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CompositeKey {
- val publicKey = parser.readValueAs()
- 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() {
+ private class PartyAndCertificateSerializer : JsonSerializer() {
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject {
writeObjectField("name", value.name)
@@ -212,100 +225,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() {
+ override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
+ }
}
- private interface JsonSerde {
- val type: Class
- val serializer: JsonSerializer
- val deserializer: JsonDeserializer
+ private class SignedTransactionDeserializer : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
+ val wrapper = parser.readValueAs()
+ 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)
+
+ @JsonSerialize(using = SerializedBytesSerializer::class)
+ @JsonDeserialize(using = SerializedBytesDeserializer::class)
+ private class SerializedBytesMixin
+
+ private class SerializedBytesSerializer : JsonSerializer>() {
+ override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
+ val deserialized = value.deserialize()
+ gen.jsonObject {
+ writeStringField("class", deserialized.javaClass.name)
+ writeObjectField("deserialized", deserialized)
}
}
}
- private inline fun 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 {
- override val type: Class = TransactionSignature::class.java
-
- override val serializer = object : StdSerializer(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(type) {
- override fun deserialize(parser: JsonParser, context: DeserializationContext): TransactionSignature {
+ private class SerializedBytesDeserializer : JsonDeserializer>() {
+ override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes {
+ return if (parser.currentToken == JsonToken.START_OBJECT) {
val mapper = parser.codec as ObjectMapper
- val json = mapper.readTree(parser)
- val by = mapper.convertValue(json["by"])
- val signatureMetadata = json.get("signatureMetadata", JsonNode::isObject, mapper, parser)
- val bytes = json.get("bytes", JsonNode::isObject, mapper, parser)
- val partialMerkleTree = json.get("partialMerkleTree", JsonNode::isObject, mapper, parser)
-
- return TransactionSignature(bytes, by, signatureMetadata, partialMerkleTree)
+ val json = parser.readValueAsTree()
+ val clazz = context.findClass(json["class"].textValue())
+ val pojo = mapper.convertValue(json["deserialized"], clazz)
+ pojo.serialize()
+ } else {
+ SerializedBytes(parser.binaryValue)
}
}
}
- private object SignedTransactionSerde : JsonSerde {
- override val type: Class = SignedTransaction::class.java
+ @ToStringSerialize
+ private interface X500PrincipalMixin
- override val serializer = object : StdSerializer(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() {
+ 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(type) {
- override fun deserialize(parser: JsonParser, context: DeserializationContext): SignedTransaction {
- val mapper = parser.codec as ObjectMapper
- val json = mapper.readTree(parser)
-
- val txBits = json.get("txBits", JsonNode::isTextual, mapper, parser)
- val signatures = json.get("signatures", JsonNode::isArray, mapper, parser)
-
- return SignedTransaction(SerializedBytes(txBits), signatures)
- }
- }
-
- private class TransactionSignatures : ArrayList()
}
-
-
- //
- // 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() {
- override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
- generator.writeString(obj.toString())
+ private class X509CertificateDeserializer : JsonDeserializer() {
+ private val certFactory = CertificateFactory.getInstance("X.509")
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): X509Certificate {
+ val encoded = parser.readValueAsTree()["encoded"]
+ return certFactory.generateCertificate(encoded.binaryValue().inputStream()) as X509Certificate
}
}
+ @JsonSerialize(using = CertPathSerializer::class)
+ @JsonDeserialize(using = CertPathDeserializer::class)
+ private interface CertPathMixin
+
+ private class CertPathSerializer : JsonSerializer() {
+ override fun serialize(value: CertPath, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.writeObject(CertPathWrapper(value.type, uncheckedCast(value.certificates)))
+ }
+ }
+
+ private class CertPathDeserializer : JsonDeserializer() {
+ private val certFactory = CertificateFactory.getInstance("X.509")
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CertPath {
+ val wrapper = parser.readValueAs()
+ return certFactory.generateCertPath(wrapper.certificates)
+ }
+ }
+
+ private data class CertPathWrapper(val type: String, val certificates: List) {
+ 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() {
override fun serialize(value: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
@@ -320,6 +379,9 @@ object JacksonSupport {
}
}
+ @JsonSerialize(using = PartySerializer::class)
+ private interface PartyMixin
+
@Deprecated("This is an internal class, do not use")
object PartySerializer : JsonSerializer() {
override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) {
@@ -354,13 +416,9 @@ object JacksonSupport {
}
}
- @Deprecated("This is an internal class, do not use")
- // This is no longer used
- object CordaX500NameSerializer : JsonSerializer() {
- 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() {
@@ -373,13 +431,9 @@ object JacksonSupport {
}
}
- @Deprecated("This is an internal class, do not use")
- // This is no longer used
- object NodeInfoSerializer : JsonSerializer() {
- 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() {
@@ -390,17 +444,10 @@ object JacksonSupport {
}
}
- @Deprecated("This is an internal class, do not use")
- // This is no longer used
- object SecureHashSerializer : JsonSerializer() {
- 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 : JsonDeserializer() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
@@ -412,6 +459,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() {
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
@@ -430,13 +481,9 @@ object JacksonSupport {
}
}
- @Deprecated("This is an internal class, do not use")
- // This is no longer used
- object AmountSerializer : JsonSerializer>() {
- 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>() {
@@ -444,20 +491,30 @@ object JacksonSupport {
return if (parser.currentToken == JsonToken.VALUE_STRING) {
Amount.parseCurrency(parser.text)
} else {
- try {
- val tree = parser.readValueAsTree()
- 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(token)
- Amount(quantity.longValue(), currency)
- } catch (e: Exception) {
- throw JsonParseException(parser, "Invalid amount", e)
- }
+ val wrapper = parser.readValueAs()
+ 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() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): OpaqueBytes {
@@ -465,6 +522,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() {
+ 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() {
+ 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() {
+ 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() {
+ 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>() {
+ 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() {
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
@@ -477,7 +575,7 @@ object JacksonSupport {
abstract class SignedTransactionMixin {
@JsonIgnore abstract fun getTxBits(): SerializedBytes
@JsonProperty("signatures") protected abstract fun getSigs(): List
- @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
diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt
index 04284557e9..a1d718193a 100644
--- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt
@@ -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 SimpleModule.addSerAndDeser(serializer: JsonSerializer, deserializer: JsonDeserializer) {
addSerializer(T::class.java, serializer)
@@ -19,3 +22,5 @@ inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerato
}
inline fun JsonParser.readValueAs(): T = readValueAs(T::class.java)
+
+inline fun JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
index 061d293528..8cc41dfdb7 100644
--- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
@@ -10,14 +10,16 @@
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.*
@@ -26,14 +28,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.*
@@ -44,19 +48,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
@@ -64,7 +78,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
@@ -76,44 +90,29 @@ class JacksonSupportTest {
doReturn(cordappProvider).whenever(services).cordappProvider
}
- private class Dummy(val notional: Amount)
-
@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(Amount.parseCurrency("£25000000")).textValue()).isEqualTo("25000000.00 GBP")
+ assertThat(mapper.valueToTree(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>(old)).isEqualTo(Amount(2_500_000_000, USD))
+ assertThat(mapper.convertValue>(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(byteSequence)
+ assertThat(json.binaryValue()).containsExactly(1, 2)
+ assertThat(json.asText()).isEqualTo(byteArrayOf(1, 2).toBase64())
+ assertThat(mapper.convertValue(json)).isEqualTo(byteSequence)
}
@Test
@@ -125,6 +124,105 @@ class JacksonSupportTest {
assertThat(mapper.convertValue(json)).isEqualTo(opaqueBytes)
}
+ @Test
+ fun SerializedBytes() {
+ val data = TestData(BOB_NAME, "Summary", SubTestData(1234))
+ val serializedBytes = data.serialize()
+ val json = mapper.valueToTree(serializedBytes)
+ println(mapper.writeValueAsString(json))
+ assertThat(json["class"].textValue()).isEqualTo(TestData::class.java.name)
+ assertThat(json["deserialized"].valueAs(mapper)).isEqualTo(data)
+ // Check that the entire JSON object can be converted back to the same SerializedBytes
+ assertThat(mapper.convertValue>(json)).isEqualTo(serializedBytes)
+ assertThat(mapper.convertValue>(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(javaClass.getResource("class-not-on-classpath-data").readBytes())
+ val json = mapper.valueToTree(serializedBytes)
+ println(mapper.writeValueAsString(json))
+ assertThat(json["class"].textValue()).isEqualTo("net.corda.client.jackson.JacksonSupportTest\$ClassNotOnClasspath")
+ assertThat(json["deserialized"].valueAs