Cleanup and improvements to the serialisation format of JacksonSupport (needed for CORDA-1238) (#3102)

Also deprecated all the public members that shouldn't have leaked into the public API.
This commit is contained in:
Shams Asari 2018-05-09 21:42:55 +01:00 committed by GitHub
parent 3f21c47f39
commit 3bb95c3ed1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 488 additions and 281 deletions

View File

@ -3618,9 +3618,9 @@ public final class net.corda.client.jackson.JacksonSupport extends java.lang.Obj
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createDefaultMapper(net.corda.core.messaging.CordaRPCOps)
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createDefaultMapper(net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory)
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createDefaultMapper(net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean)
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService)
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory)
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory, boolean)
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService)
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory)
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createInMemoryMapper(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory, boolean)
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createNonRpcMapper()
@org.jetbrains.annotations.NotNull public static final com.fasterxml.jackson.databind.ObjectMapper createNonRpcMapper(com.fasterxml.jackson.core.JsonFactory)
@org.jetbrains.annotations.NotNull public final com.fasterxml.jackson.databind.Module getCordaModule()
@ -3650,7 +3650,7 @@ public static final class net.corda.client.jackson.JacksonSupport$CordaX500NameS
public void serialize(net.corda.core.identity.CordaX500Name, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
public static final net.corda.client.jackson.JacksonSupport$CordaX500NameSerializer INSTANCE
##
public static final class net.corda.client.jackson.JacksonSupport$IdentityObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper
@net.corda.core.DoNotImplement public static final class net.corda.client.jackson.JacksonSupport$IdentityObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper
public <init>(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory, boolean)
public final boolean getFuzzyIdentityMatch()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.IdentityService getIdentityService()
@ -3658,9 +3658,9 @@ public static final class net.corda.client.jackson.JacksonSupport$IdentityObject
@org.jetbrains.annotations.Nullable public net.corda.core.identity.Party partyFromKey(java.security.PublicKey)
@org.jetbrains.annotations.Nullable public net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name)
##
public static final class net.corda.client.jackson.JacksonSupport$NoPartyObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper
@net.corda.core.DoNotImplement public static final class net.corda.client.jackson.JacksonSupport$NoPartyObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper
public <init>(com.fasterxml.jackson.core.JsonFactory)
@org.jetbrains.annotations.NotNull public Void partiesFromName(String)
@org.jetbrains.annotations.NotNull public Set partiesFromName(String)
@org.jetbrains.annotations.Nullable public net.corda.core.identity.Party partyFromKey(java.security.PublicKey)
@org.jetbrains.annotations.Nullable public net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name)
##
@ -3684,7 +3684,7 @@ public static final class net.corda.client.jackson.JacksonSupport$PartyDeseriali
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
public static final net.corda.client.jackson.JacksonSupport$PartyDeserializer INSTANCE
##
public static interface net.corda.client.jackson.JacksonSupport$PartyObjectMapper
@net.corda.core.DoNotImplement public static interface net.corda.client.jackson.JacksonSupport$PartyObjectMapper
@org.jetbrains.annotations.NotNull public abstract Set partiesFromName(String)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party partyFromKey(java.security.PublicKey)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name)
@ -3701,7 +3701,7 @@ public static final class net.corda.client.jackson.JacksonSupport$PublicKeySeria
public void serialize(java.security.PublicKey, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
public static final net.corda.client.jackson.JacksonSupport$PublicKeySerializer INSTANCE
##
public static final class net.corda.client.jackson.JacksonSupport$RpcObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper
@net.corda.core.DoNotImplement public static final class net.corda.client.jackson.JacksonSupport$RpcObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper implements net.corda.client.jackson.JacksonSupport$PartyObjectMapper
public <init>(net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean)
public final boolean getFuzzyIdentityMatch()
@org.jetbrains.annotations.NotNull public final net.corda.core.messaging.CordaRPCOps getRpc()

View File

@ -40,7 +40,6 @@ buildscript {
ext.jackson_version = '2.9.3'
ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.json_version = '20180130'
ext.assertj_version = '3.8.0'
ext.slf4j_version = '1.7.25'
ext.log4j_version = '2.9.1'

View File

@ -2,56 +2,42 @@ package net.corda.client.jackson
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
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.DoNotImplement
import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.AddressFormatException
import net.corda.core.crypto.Base58
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.PartialMerkleTree
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.*
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.toStringShort
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.identity.*
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.base64ToByteArray
import net.corda.core.utilities.toBase64
import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.core.utilities.toBase58String
import java.math.BigDecimal
import java.security.PublicKey
import java.util.*
@ -62,73 +48,68 @@ import java.util.*
*
* Note that Jackson can also be used to serialise/deserialise other formats such as Yaml and XML.
*/
@Suppress("DEPRECATION")
object JacksonSupport {
// TODO: This API could use some tidying up - there should really only need to be one kind of mapper.
// If you change this API please update the docs in the docsite (json.rst)
@DoNotImplement
interface PartyObjectMapper {
fun wellKnownPartyFromX500Name(name: CordaX500Name): Party?
fun partyFromKey(owningKey: PublicKey): Party?
fun partiesFromName(query: String): Set<Party>
fun nodeInfoFromParty(party: AbstractParty): NodeInfo?
}
class RpcObjectMapper(val rpc: CordaRPCOps, factory: JsonFactory, val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) {
@Deprecated("This is an internal class, do not use", replaceWith = ReplaceWith("JacksonSupport.createDefaultMapper"))
class RpcObjectMapper(val rpc: CordaRPCOps,
factory: JsonFactory,
val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) {
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = rpc.wellKnownPartyFromX500Name(name)
override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey)
override fun partiesFromName(query: String) = rpc.partiesFromName(query, fuzzyIdentityMatch)
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = rpc.nodeInfoFromParty(party)
}
class IdentityObjectMapper(val identityService: IdentityService, factory: JsonFactory, val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) {
@Deprecated("This is an internal class, do not use")
class IdentityObjectMapper(val identityService: IdentityService,
factory: JsonFactory,
val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) {
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = identityService.wellKnownPartyFromX500Name(name)
override fun partyFromKey(owningKey: PublicKey): Party? = identityService.partyFromKey(owningKey)
override fun partiesFromName(query: String) = identityService.partiesFromName(query, fuzzyIdentityMatch)
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null
}
@Deprecated("This is an internal class, do not use", replaceWith = ReplaceWith("JacksonSupport.createNonRpcMapper"))
class NoPartyObjectMapper(factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = throw UnsupportedOperationException()
override fun partyFromKey(owningKey: PublicKey): Party? = throw UnsupportedOperationException()
override fun partiesFromName(query: String) = throw UnsupportedOperationException()
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = null
override fun partyFromKey(owningKey: PublicKey): Party? = null
override fun partiesFromName(query: String): Set<Party> = emptySet()
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null
}
val cordaModule: Module by lazy {
SimpleModule("core").apply {
addSerializer(AnonymousParty::class.java, AnonymousPartySerializer)
addDeserializer(AnonymousParty::class.java, AnonymousPartyDeserializer)
addSerializer(Party::class.java, PartySerializer)
addDeserializer(Party::class.java, PartyDeserializer)
addSerAndDeser(AnonymousPartySerializer, AnonymousPartyDeserializer)
addSerAndDeser(PartySerializer, PartyDeserializer)
addDeserializer(AbstractParty::class.java, PartyDeserializer)
addSerializer(BigDecimal::class.java, ToStringSerializer)
addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
addSerializer(SecureHash::class.java, SecureHashSerializer)
addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer)
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
// Public key types
addSerializer(PublicKey::class.java, PublicKeySerializer)
addDeserializer(PublicKey::class.java, PublicKeyDeserializer)
// For NodeInfo
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
addSerializer(NodeInfo::class.java, NodeInfoSerializer)
addSerAndDeser<BigDecimal>(toStringSerializer, NumberDeserializers.BigDecimalDeserializer())
addSerAndDeser<SecureHash.SHA256>(toStringSerializer, SecureHashDeserializer())
addSerAndDeser(toStringSerializer, AmountDeserializer)
addSerAndDeser(OpaqueBytesSerializer, OpaqueBytesDeserializer)
addSerAndDeser(toStringSerializer, CordaX500NameDeserializer)
addSerAndDeser(PublicKeySerializer, PublicKeyDeserializer)
addDeserializer(CompositeKey::class.java, CompositeKeyDeseriaizer)
addSerAndDeser(toStringSerializer, NetworkHostAndPortDeserializer)
// TODO Add deserialization which follows the same lookup logic as Party
addSerializer(PartyAndCertificate::class.java, PartyAndCertificateSerializer)
addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
// For Amount
addSerializer(Amount::class.java, AmountSerializer)
addDeserializer(Amount::class.java, AmountDeserializer)
// For OpaqueBytes
addDeserializer(OpaqueBytes::class.java, OpaqueBytesDeserializer)
addSerializer(OpaqueBytes::class.java, OpaqueBytesSerializer)
listOf(TransactionSignatureSerde, SignedTransactionSerde).forEach { serde -> serde.applyTo(this) }
// For X.500 distinguished names
addDeserializer(CordaX500Name::class.java, CordaX500NameDeserializer)
addSerializer(CordaX500Name::class.java, CordaX500NameSerializer)
// Mixins for transaction types to prevent some properties from being serialized
// Using mixins to fine-tune the default serialised output
setMixInAnnotation(WireTransaction::class.java, WireTransactionMixin::class.java)
setMixInAnnotation(NodeInfo::class.java, NodeInfoMixin::class.java)
}
}
@ -141,8 +122,11 @@ object JacksonSupport {
*/
@JvmStatic
@JvmOverloads
fun createDefaultMapper(rpc: CordaRPCOps, factory: JsonFactory = JsonFactory(),
fuzzyIdentityMatch: Boolean = false): ObjectMapper = configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch))
fun createDefaultMapper(rpc: CordaRPCOps,
factory: JsonFactory = JsonFactory(),
fuzzyIdentityMatch: Boolean = false): ObjectMapper {
return configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch))
}
/** For testing or situations where deserialising parties is not required */
@JvmStatic
@ -156,20 +140,72 @@ object JacksonSupport {
* match an identity known from the network map. If true, the name is matched more leniently but if the match
* is ambiguous a [JsonParseException] is thrown.
*/
@Deprecated("This is an internal method, do not use")
@JvmStatic
@JvmOverloads
fun createInMemoryMapper(identityService: IdentityService, factory: JsonFactory = JsonFactory(),
fuzzyIdentityMatch: Boolean = false) = configureMapper(IdentityObjectMapper(identityService, factory, fuzzyIdentityMatch))
fun createInMemoryMapper(identityService: IdentityService,
factory: JsonFactory = JsonFactory(),
fuzzyIdentityMatch: Boolean = false): ObjectMapper {
return configureMapper(IdentityObjectMapper(identityService, factory, fuzzyIdentityMatch))
}
private fun configureMapper(mapper: ObjectMapper): ObjectMapper = mapper.apply {
@CordaInternal
@VisibleForTesting
internal fun createPartyObjectMapper(partyObjectMapper: PartyObjectMapper, factory: JsonFactory = JsonFactory()): ObjectMapper {
val mapper = object : ObjectMapper(factory), PartyObjectMapper by partyObjectMapper {}
return configureMapper(mapper)
}
private fun configureMapper(mapper: ObjectMapper): ObjectMapper {
return mapper.apply {
enable(SerializationFeature.INDENT_OUTPUT)
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
registerModule(JavaTimeModule())
registerModule(JavaTimeModule().apply {
addSerializer(Date::class.java, DateSerializer)
})
registerModule(cordaModule)
registerModule(KotlinModule())
}
}
private val toStringSerializer = com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance
private object DateSerializer : JsonSerializer<Date>() {
override fun serialize(value: Date, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(value.toInstant())
}
}
private object NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
return NetworkHostAndPort.parse(parser.text)
}
}
private object CompositeKeyDeseriaizer : JsonDeserializer<CompositeKey>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CompositeKey {
val publicKey = parser.readValueAs<PublicKey>()
return publicKey as? CompositeKey ?: throw JsonParseException(parser, "Not a CompositeKey: $publicKey")
}
}
private object PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject {
writeObjectField("name", value.name)
writeObjectField("owningKey", value.owningKey)
// TODO Add configurable option to output the certPath
}
}
}
@Suppress("unused")
private interface NodeInfoMixin {
@get:JsonIgnore val legalIdentities: Any // This is already covered by legalIdentitiesAndCerts
}
private interface JsonSerde<TYPE> {
val type: Class<TYPE>
@ -185,7 +221,6 @@ object JacksonSupport {
}
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\".")
}
@ -196,14 +231,12 @@ object JacksonSupport {
override val type: Class<TransactionSignature> = TransactionSignature::class.java
override val serializer = object : StdSerializer<TransactionSignature>(type) {
override fun serialize(value: TransactionSignature, json: JsonGenerator, serializers: SerializerProvider) {
with(json) {
writeStartObject()
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)
writeEndObject()
}
}
}
@ -212,11 +245,7 @@ object JacksonSupport {
override fun deserialize(parser: JsonParser, context: DeserializationContext): TransactionSignature {
val mapper = parser.codec as ObjectMapper
val json = mapper.readTree<JsonNode>(parser)
if (json.get("by")?.isTextual != true) {
JsonParseException(parser, "Missing required text field \"by\".")
}
val by = PublicKeyDeserializer.deserializeValue(json.get("by").textValue())
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)
@ -230,12 +259,10 @@ object JacksonSupport {
override val type: Class<SignedTransaction> = SignedTransaction::class.java
override val serializer = object : StdSerializer<SignedTransaction>(type) {
override fun serialize(value: SignedTransaction, json: JsonGenerator, serializers: SerializerProvider) {
with(json) {
writeStartObject()
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.jsonObject {
writeObjectField("txBits", value.txBits.bytes)
writeObjectField("signatures", value.sigs)
writeEndObject()
}
}
}
@ -255,110 +282,106 @@ object JacksonSupport {
private class TransactionSignatures : ArrayList<TransactionSignature>()
}
//
// The following should not have been made public and are thus deprecated with warnings.
//
@Deprecated("No longer used as jackson already has a toString serializer",
replaceWith = ReplaceWith("com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance"))
object ToStringSerializer : JsonSerializer<Any>() {
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
@Deprecated("This is an internal class, do not use")
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
PublicKeySerializer.serialize(obj.owningKey, generator, provider)
override fun serialize(value: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeObject(value.owningKey)
}
}
@Deprecated("This is an internal class, do not use")
object AnonymousPartyDeserializer : JsonDeserializer<AnonymousParty>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): AnonymousParty {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val key = PublicKeyDeserializer.deserialize(parser, context)
return AnonymousParty(key)
return AnonymousParty(parser.readValueAs(PublicKey::class.java))
}
}
@Deprecated("This is an internal class, do not use")
object PartySerializer : JsonSerializer<Party>() {
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.name.toString())
override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) {
// TODO Add configurable option to output this as an object which includes the owningKey
generator.writeObject(value.name)
}
}
@Deprecated("This is an internal class, do not use")
object PartyDeserializer : JsonDeserializer<Party>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val mapper = parser.codec as PartyObjectMapper
// The comma character is invalid in base64, and required as a separator for X.500 names. As Corda
// The comma character is invalid in Base58, and required as a separator for X.500 names. As Corda
// X.500 names all involve at least three attributes (organisation, locality, country), they must
// include a comma. As such we can use it as a distinguisher between the two types.
return if (parser.text.contains(",")) {
return if ("," in parser.text) {
val principal = CordaX500Name.parse(parser.text)
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
} else {
val nameMatches = mapper.partiesFromName(parser.text)
if (nameMatches.isEmpty()) {
val derBytes = try {
parser.text.base64ToByteArray()
} catch (e: AddressFormatException) {
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a base64 encoded public key: " + e.message)
}
val key = try {
Crypto.decodePublicKey(derBytes)
} catch (e: Exception) {
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a valid public key: " + e.message)
}
mapper.partyFromKey(key) ?: throw JsonParseException(parser, "Could not find a Party with key ${key.toStringShort()}")
val publicKey = parser.readValueAs<PublicKey>()
mapper.partyFromKey(publicKey)
?: throw JsonParseException(parser, "Could not find a Party with key ${publicKey.toStringShort()}")
} else if (nameMatches.size == 1) {
nameMatches.first()
} else {
throw JsonParseException(parser, "Ambiguous name match '${parser.text}': could be any of " + nameMatches.map { it.name }.joinToString(" ... or ..."))
throw JsonParseException(parser, "Ambiguous name match '${parser.text}': could be any of " +
nameMatches.map { it.name }.joinToString(" ... or ... "))
}
}
}
}
@Deprecated("This is an internal class, do not use")
// This is no longer used
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() {
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
@Deprecated("This is an internal class, do not use")
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
return try {
CordaX500Name.parse(parser.text)
} catch (ex: IllegalArgumentException) {
throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${ex.message}", ex)
} catch (e: IllegalArgumentException) {
throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${e.message}", e)
}
}
}
@Deprecated("This is an internal class, do not use")
// This is no longer used
object NodeInfoSerializer : JsonSerializer<NodeInfo>() {
override fun serialize(value: NodeInfo, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(Base58.encode(value.serialize().bytes))
}
}
@Deprecated("This is an internal class, do not use")
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): NodeInfo {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
try {
return Base58.decode(parser.text).deserialize<NodeInfo>()
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid NodeInfo ${parser.text}: ${e.message}")
}
val mapper = parser.codec as PartyObjectMapper
val party = parser.readValueAs<AbstractParty>()
return mapper.nodeInfoFromParty(party) ?: throw JsonParseException(parser, "Cannot find node with $party")
}
}
@Deprecated("This is an internal class, do not use")
// This is no longer used
object SecureHashSerializer : JsonSerializer<SecureHash>() {
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
@ -368,11 +391,9 @@ object JacksonSupport {
/**
* Implemented as a class so that we can instantiate for T.
*/
@Deprecated("This is an internal class, do not use")
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
try {
return uncheckedCast(SecureHash.parse(parser.text))
} catch (e: Exception) {
@ -381,69 +402,72 @@ object JacksonSupport {
}
}
@Deprecated("This is an internal class, do not use")
object PublicKeySerializer : JsonSerializer<PublicKey>() {
override fun serialize(obj: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.encoded.toBase64())
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(value.toBase58String())
}
}
@Deprecated("This is an internal class, do not use")
object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): PublicKey {
return deserializeValue(parser.text, parser)
}
internal fun deserializeValue(value: String, parser: JsonParser? = null): PublicKey {
return try {
val derBytes = value.base64ToByteArray()
Crypto.decodePublicKey(derBytes)
parsePublicKeyBase58(parser.text)
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid public key $value: ${e.message}")
throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}")
}
}
}
@Deprecated("This is an internal class, do not use")
// This is no longer used
object AmountSerializer : JsonSerializer<Amount<*>>() {
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toString())
}
}
@Deprecated("This is an internal class, do not use")
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
return if (parser.currentToken == JsonToken.VALUE_STRING) {
Amount.parseCurrency(parser.text)
} else {
try {
return Amount.parseCurrency(parser.text)
} catch (e: Exception) {
try {
val tree = parser.readValueAsTree<JsonNode>()
require(tree["quantity"].canConvertToLong() && tree["token"].asText().isNotBlank())
val quantity = tree["quantity"].asLong()
val token = tree["token"].asText()
val tree = parser.readValueAsTree<ObjectNode>()
val quantity = tree["quantity"].apply { require(canConvertToLong()) }
val token = tree["token"]
// Attempt parsing as a currency token. TODO: This needs thought about how to extend to other token types.
val currency = Currency.getInstance(token)
return Amount(quantity, currency)
} catch (e2: Exception) {
throw JsonParseException(parser, "Invalid amount ${parser.text}", e2)
val currency = (parser.codec as ObjectMapper).convertValue<Currency>(token)
Amount(quantity.longValue(), currency)
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid amount", e)
}
}
}
}
@Deprecated("This is an internal class, do not use")
object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): OpaqueBytes {
return OpaqueBytes(parser.text.toByteArray())
return OpaqueBytes(parser.binaryValue)
}
}
@Deprecated("This is an internal class, do not use")
object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() {
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeBinary(value.bytes)
}
}
@Deprecated("This is an internal class, do not use")
@Suppress("unused")
abstract class SignedTransactionMixin {
@JsonIgnore abstract fun getTxBits(): SerializedBytes<CoreTransaction>
@JsonProperty("signatures") protected abstract fun getSigs(): List<TransactionSignature>
@JsonProperty protected abstract fun getTransaction(): CoreTransaction
@JsonProperty protected abstract fun getTransaction(): CoreTransaction // TODO It seems this should be coreTransaction
@JsonIgnore abstract fun getTx(): WireTransaction
@JsonIgnore abstract fun getNotaryChangeTx(): NotaryChangeWireTransaction
@JsonIgnore abstract fun getInputs(): List<StateRef>
@ -452,6 +476,8 @@ object JacksonSupport {
@JsonIgnore abstract fun getRequiredSigningKeys(): Set<PublicKey>
}
@Deprecated("This is an internal class, do not use")
@Suppress("unused")
abstract class WireTransactionMixin {
@JsonIgnore abstract fun getMerkleTree(): MerkleTree
@JsonIgnore abstract fun getAvailableComponents(): List<Any>
@ -459,4 +485,3 @@ object JacksonSupport {
@JsonIgnore abstract fun getOutputStates(): List<ContractState>
}
}

View File

@ -0,0 +1,21 @@
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.JsonSerializer
import com.fasterxml.jackson.databind.module.SimpleModule
inline fun <reified T : Any> SimpleModule.addSerAndDeser(serializer: JsonSerializer<in T>, deserializer: JsonDeserializer<T>) {
addSerializer(T::class.java, serializer)
addDeserializer(T::class.java, deserializer)
}
inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerator.() -> Unit) {
fieldName?.let { writeFieldName(it) }
writeStartObject()
gen()
writeEndObject()
}
inline fun <reified T> JsonParser.readValueAs(): T = readValueAs(T::class.java)

View File

@ -1,49 +1,61 @@
package net.corda.client.jackson
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.node.ArrayNode
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.module.kotlin.convertValue
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Amount
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
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.node.NodeInfo
import net.corda.core.node.ServiceHub
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.finance.USD
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.*
import net.corda.testing.internal.createNodeInfoAndSigned
import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.math.BigInteger
import java.security.PublicKey
import java.util.*
import kotlin.collections.ArrayList
import kotlin.test.assertEquals
class JacksonSupportTest {
private companion object {
val SEED = BigInteger.valueOf(20170922L)!!
val mapper = JacksonSupport.createNonRpcMapper()
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")).party
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val partyObjectMapper = TestPartyObjectMapper()
private val mapper = JacksonSupport.createPartyObjectMapper(partyObjectMapper)
private lateinit var services: ServiceHub
private lateinit var cordappProvider: CordappProvider
@ -54,37 +66,10 @@ class JacksonSupportTest {
doReturn(cordappProvider).whenever(services).cordappProvider
}
@Test
fun `should serialize Composite keys`() {
val expected = "\"MIHAMBUGE2mtoq+J1bjir/ONk6yd5pab0FoDgaYAMIGiAgECMIGcMDIDLQAwKjAFBgMrZXADIQAgIX1QlJRgaLlD0ttLlJF5kNqT/7P7QwCvrWc9+/248gIBATAyAy0AMCowBQYDK2VwAyEAqS0JPGlzdviBZjB9FaNY+w6cVs3/CQ2A5EimE9Lyng4CAQEwMgMtADAqMAUGAytlcAMhALq4GG0gBQZIlaKE6ucooZsuoKUbH4MtGSmA6cwj136+AgEB\""
val innerKeys = (1..3).map { i ->
Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED.plus(BigInteger.valueOf(i.toLong()))).public
}
// Build a 2 of 3 composite key
val publicKey = CompositeKey.Builder().let {
innerKeys.forEach { key -> it.addKey(key, 1) }
it.build(2)
}
val serialized = mapper.writeValueAsString(publicKey)
assertEquals(expected, serialized)
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
assertEquals(publicKey, parsedKey)
}
private class Dummy(val notional: Amount<Currency>)
@Test
fun `should serialize EdDSA keys`() {
val expected = "\"MCowBQYDK2VwAyEACFTgLk1NOqYXAfxLoR7ctSbZcl9KMXu58Mq31Kv1Dwk=\""
val publicKey = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED).public
val serialized = mapper.writeValueAsString(publicKey)
assertEquals(expected, serialized)
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
assertEquals(publicKey, parsedKey)
}
@Test
fun readAmount() {
fun `read Amount`() {
val oldJson = """
{
"notional": {
@ -100,27 +85,186 @@ class JacksonSupportTest {
}
@Test
fun writeAmount() {
fun `write Amount`() {
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT)
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$25000000"))))
assertEquals("""{"notional":"25000000.00 GBP"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("£25000000"))))
assertEquals("""{"notional":"250000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$250000"))))
}
@Test
fun `wire transaction can be serialized and de-serialized`() {
fun SignedTransaction() {
val attachmentRef = SecureHash.randomSHA256()
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(testNetworkParameters()).whenever(services).networkParameters
val writer = mapper.writer()
val transaction = makeDummyTx()
val json = writer.writeValueAsString(transaction)
val stx = makeDummyStx()
val json = writer.writeValueAsString(stx)
val deserializedTransaction = mapper.readValue(json, SignedTransaction::class.java)
assertThat(deserializedTransaction).isEqualTo(transaction)
assertThat(deserializedTransaction).isEqualTo(stx)
}
private fun makeDummyTx(): SignedTransaction {
@Test
fun OpaqueBytes() {
val opaqueBytes = OpaqueBytes(secureRandomBytes(128))
val json = mapper.valueToTree<BinaryNode>(opaqueBytes)
assertThat(json.binaryValue()).isEqualTo(opaqueBytes.bytes)
assertThat(json.asText()).isEqualTo(opaqueBytes.bytes.toBase64())
assertThat(mapper.convertValue<OpaqueBytes>(json)).isEqualTo(opaqueBytes)
}
@Test
fun CordaX500Name() {
testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US"))
}
@Test
fun `SecureHash SHA256`() {
testToStringSerialisation(SecureHash.randomSHA256())
}
@Test
fun NetworkHostAndPort() {
testToStringSerialisation(NetworkHostAndPort("localhost", 9090))
}
@Test
fun UUID() {
testToStringSerialisation(UUID.randomUUID())
}
@Test
fun `Date is treated as Instant`() {
val date = Date()
val json = mapper.valueToTree<TextNode>(date)
assertThat(json.textValue()).isEqualTo(date.toInstant().toString())
assertThat(mapper.convertValue<Date>(json)).isEqualTo(date)
}
@Test
fun `Party serialization`() {
val json = mapper.valueToTree<TextNode>(MINI_CORP.party)
assertThat(json.textValue()).isEqualTo(MINI_CORP.name.toString())
}
@Test
fun `Party deserialization on full name`() {
fun convertToParty() = mapper.convertValue<Party>(TextNode(MINI_CORP.name.toString()))
assertThatThrownBy { convertToParty() }
partyObjectMapper.identities += MINI_CORP.party
assertThat(convertToParty()).isEqualTo(MINI_CORP.party)
}
@Test
fun `Party deserialization on part of name`() {
fun convertToParty() = mapper.convertValue<Party>(TextNode(MINI_CORP.name.organisation))
assertThatThrownBy { convertToParty() }
partyObjectMapper.identities += MINI_CORP.party
assertThat(convertToParty()).isEqualTo(MINI_CORP.party)
}
@Test
fun `Party deserialization on public key`() {
fun convertToParty() = mapper.convertValue<Party>(TextNode(MINI_CORP.publicKey.toBase58String()))
assertThatThrownBy { convertToParty() }
partyObjectMapper.identities += MINI_CORP.party
assertThat(convertToParty()).isEqualTo(MINI_CORP.party)
}
@Test
fun PublicKey() {
val json = mapper.valueToTree<TextNode>(MINI_CORP.publicKey)
assertThat(json.textValue()).isEqualTo(MINI_CORP.publicKey.toBase58String())
assertThat(mapper.convertValue<PublicKey>(json)).isEqualTo(MINI_CORP.publicKey)
}
@Test
fun `EdDSA public key`() {
val publicKey = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED).public
val json = mapper.valueToTree<TextNode>(publicKey)
assertThat(json.textValue()).isEqualTo(publicKey.toBase58String())
assertThat(mapper.convertValue<PublicKey>(json)).isEqualTo(publicKey)
}
@Test
fun CompositeKey() {
val innerKeys = (1..3).map { i ->
Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED + i.toBigInteger()).public
}
// Build a 2 of 3 composite key
val publicKey = CompositeKey.Builder().let {
innerKeys.forEach { key -> it.addKey(key, 1) }
it.build(2)
}
val json = mapper.valueToTree<TextNode>(publicKey)
assertThat(json.textValue()).isEqualTo(publicKey.toBase58String())
assertThat(mapper.convertValue<CompositeKey>(json)).isEqualTo(publicKey)
}
@Test
fun AnonymousParty() {
val anon = AnonymousParty(ALICE_PUBKEY)
val json = mapper.valueToTree<TextNode>(anon)
assertThat(json.textValue()).isEqualTo(ALICE_PUBKEY.toBase58String())
assertThat(mapper.convertValue<AnonymousParty>(json)).isEqualTo(anon)
}
@Test
fun `PartyAndCertificate serialisation`() {
val json = mapper.valueToTree<ObjectNode>(MINI_CORP.identity)
assertThat(json.fieldNames()).containsOnly("name", "owningKey")
assertThat(mapper.convertValue<CordaX500Name>(json["name"])).isEqualTo(MINI_CORP.name)
assertThat(mapper.convertValue<PublicKey>(json["owningKey"])).isEqualTo(MINI_CORP.publicKey)
}
@Test
fun `NodeInfo serialisation`() {
val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
val json = mapper.valueToTree<ObjectNode>(nodeInfo)
assertThat(json.fieldNames()).containsOnly("addresses", "legalIdentitiesAndCerts", "platformVersion", "serial")
val address = (json["addresses"] as ArrayNode).also { assertThat(it).hasSize(1) }[0]
assertThat(mapper.convertValue<NetworkHostAndPort>(address)).isEqualTo(nodeInfo.addresses[0])
val identity = (json["legalIdentitiesAndCerts"] as ArrayNode).also { assertThat(it).hasSize(1) }[0]
assertThat(mapper.convertValue<CordaX500Name>(identity["name"])).isEqualTo(ALICE_NAME)
assertThat(mapper.convertValue<Int>(json["platformVersion"])).isEqualTo(nodeInfo.platformVersion)
assertThat(mapper.convertValue<Long>(json["serial"])).isEqualTo(nodeInfo.serial)
}
@Test
fun `NodeInfo deserialisation on name`() {
val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
fun convertToNodeInfo() = mapper.convertValue<NodeInfo>(TextNode(ALICE_NAME.toString()))
assertThatThrownBy { convertToNodeInfo() }
partyObjectMapper.identities += nodeInfo.legalIdentities
partyObjectMapper.nodes += nodeInfo
assertThat(convertToNodeInfo()).isEqualTo(nodeInfo)
}
@Test
fun `NodeInfo deserialisation on public key`() {
val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
fun convertToNodeInfo() = mapper.convertValue<NodeInfo>(TextNode(nodeInfo.legalIdentities[0].owningKey.toBase58String()))
assertThatThrownBy { convertToNodeInfo() }
partyObjectMapper.identities += nodeInfo.legalIdentities
partyObjectMapper.nodes += nodeInfo
assertThat(convertToNodeInfo()).isEqualTo(nodeInfo)
}
private fun makeDummyStx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)
val signatures = listOf(
@ -129,4 +273,27 @@ class JacksonSupportTest {
)
return SignedTransaction(wtx, signatures)
}
private inline fun <reified T : Any> testToStringSerialisation(value: T) {
val json = mapper.valueToTree<TextNode>(value)
assertThat(json.textValue()).isEqualTo(value.toString())
assertThat(mapper.convertValue<T>(json)).isEqualTo(value)
}
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
val identities = ArrayList<Party>()
val nodes = ArrayList<NodeInfo>()
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? {
return identities.find { it.name == name }
}
override fun partyFromKey(owningKey: PublicKey): Party? {
return identities.find { it.owningKey == owningKey }
}
override fun partiesFromName(query: String): Set<Party> {
return identities.filter { query in it.name.toString() }.toSet()
}
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? {
return nodes.find { party in it.legalIdentities }
}
}
}

View File

@ -8,10 +8,20 @@ Unreleased
==========
* Fixed an error thrown by NodeVaultService upon recording a transaction with a number of inputs greater than the default page size.
* ``SignedTransaction`` can now be serialized to JSON and deserialized back into an object.
* Fixed incorrect computation of ``totalStates`` from ``otherResults`` in ``NodeVaultService``.
* Changes to the JSON/YAML serialisation format from ``JacksonSupport``, which also applies to the node shell:
* ``Instant`` and ``Date`` objects are serialised as ISO-8601 formatted strings rather than timestamps
* ``PublicKey`` objects are serialised and looked up according to their Base58 encoded string
* ``Party`` objects can be deserialised by looking up their public key, in addition to their name
* ``NodeInfo`` objects are serialised as an object and can be looked up using the same mechanism as ``Party``
* ``NetworkHostAndPort`` serialised according to its ``toString()``
* ``PartyAndCertificate`` is serialised as an object containing the name and owning key
* ``SignedTransaction`` can now be serialized to JSON and deserialized back into an object.
* Several members of ``JacksonSupport`` have been deprecated to highlight that they are internal and not to be used
* Refactor AMQP Serializer to pass context object down the serialization call hierarchy. Will allow per thread
extensions to be set and used by the RPC work (Observable Context Key)

View File

@ -91,13 +91,13 @@ Windows
Windows does not provide a built-in SSH tool. An alternative such as PuTTY should be used.
The standalone shell
------------------------------
--------------------
The standalone shell is a standalone application interacting with a Corda node via RPC calls.
RPC node permissions are necessary for authentication and authorisation.
Certain operations, such as starting flows, require access to CordApps jars.
Starting the standalone shell
*************************
*****************************
Run the following command from the terminal:
@ -177,7 +177,7 @@ The format of ``config-file``:
Standalone Shell via SSH
------------------------------------------
------------------------
The standalone shell can embed an SSH server which redirects interactions via RPC calls to the Corda node.
To run SSH server use ``--sshd-port`` option when starting standalone shell or ``extensions.sshd`` entry in the configuration file.
For connection to SSH refer to `Connecting to the shell`_.
@ -253,8 +253,8 @@ Where ``newCampaign`` is a parameter of type ``Campaign``.
Mappings from strings to types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Several parameter types can automatically be mapped from strings. See the `defined parsers`_ for more information. We
cover the most common types here.
In addition to the types already supported by Jackson, several parameter types can automatically be mapped from strings.
We cover the most common types here.
Amount
~~~~~~
@ -263,23 +263,44 @@ A parameter of type ``Amount<Currency>`` can be written as either:
* A dollar ($), pound (£) or euro (€) symbol followed by the amount as a decimal
* The amount as a decimal followed by the ISO currency code (e.g. "100.12 CHF")
SecureHash
~~~~~~~~~~
A parameter of type ``SecureHash`` can be written as a hexadecimal string: ``F69A7626ACC27042FEEAE187E6BFF4CE666E6F318DC2B32BE9FAF87DF687930C``
OpaqueBytes
~~~~~~~~~~~
A parameter of type ``OpaqueBytes`` can be provided as a string, which will be automatically converted to
``OpaqueBytes``.
A parameter of type ``OpaqueBytes`` can be provided as a string in Base64.
PublicKey and CompositeKey
~~~~~~~~~~~~~~~~~~~~~~~~~~
A parameter of type ``PublicKey`` can be written as a Base58 string of its encoded format: ``GfHq2tTVk9z4eXgyQXzegw6wNsZfHcDhfw8oTt6fCHySFGp3g7XHPAyc2o6D``.
``net.corda.core.utilities.EncodingUtils.toBase58String`` will convert a ``PublicKey`` to this string format.
Party
~~~~~
A parameter of type ``Party`` can be written in several ways:
* By using the node's full name: ``"O=Monogram Bank,L=Sao Paulo,C=GB"``
* By using the full name: ``"O=Monogram Bank,L=Sao Paulo,C=GB"``
* By specifying the organisation name only: ``"Monogram Bank"``
* By specifying any other non-ambiguous part of the name: ``"Sao Paulo"`` (if only one network node is located in Sao
Paulo)
* By specifying the public key (see above)
Instant
~~~~~~~
A parameter of type ``Instant`` can be written as follows: ``"2017-12-22T00:00:00Z"``.
NodeInfo
~~~~~~~~
A parameter of type ``NodeInfo`` can be written in terms of one of its identities (see ``Party`` above)
AnonymousParty
~~~~~~~~~~~~~~
A parameter of type ``AnonymousParty`` can be written in terms of its ``PublicKey`` (see above)
NetworkHostAndPort
~~~~~~~~~~~~~~~~~~
A parameter of type ``NetworkHostAndPort`` can be written as a "host:port" string: ``"localhost:1010"``
Instant and Date
~~~~~~~~~~~~~~~~
A parameter of ``Instant`` and ``Date`` can be written as an ISO-8601 string: ``"2017-12-22T00:00:00Z"``
Examples
^^^^^^^^
@ -365,6 +386,5 @@ The shell will be enhanced over time. The currently known limitations include:
* The ``jul`` command advertises access to logs, but it doesn't work with the logging framework we're using
.. _Yaml: http://www.yaml.org/spec/1.2/spec.html
.. _defined parsers: api/kotlin/corda/net.corda.client.jackson/-jackson-support/index.html
.. _Groovy: http://groovy-lang.org/
.. _CRaSH: http://www.crashub.org/

View File

@ -1,6 +1,6 @@
{
"fixedLeg": {
"fixedRatePayer": "MCowBQYDK2VwAyEAzswVB9wd3XKVlRwpCIjwla25BE0bc9aW5t8GXWg71Pw=",
"fixedRatePayer": "GfHq2tTVk9z4eXgyUEefbHpUFfpnDvsFoZVZe3ikrLbwdRA4jebSJPykJwgw",
"notional": "$25000000",
"paymentFrequency": "SemiAnnual",
"effectiveDate": "2016-03-11",
@ -22,7 +22,7 @@
"interestPeriodAdjustment": "Adjusted"
},
"floatingLeg": {
"floatingRatePayer": "MCowBQYDK2VwAyEAa3nFfmoJUjkoLASBjpYRLz8DpAAbqXpWTCOFKj8epfw=",
"floatingRatePayer": "GfHq2tTVk9z4eXgyMYwWRYKSgGpARSquPTt8V4Z54RmNe2SJ7BUq2jSUUfvT",
"notional": {
"quantity": 2500000000,
"token": "USD"

View File

@ -64,7 +64,7 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKe
fun build(serial: Long = 1, platformVersion: Int = 1): NodeInfo {
return NodeInfo(
listOf(NetworkHostAndPort("my.${identitiesAndPrivateKeys[0].first.party.name.organisation}.com", 1234)),
listOf(NetworkHostAndPort("my.${identitiesAndPrivateKeys[0].first.party.name.organisation.replace(' ', '-')}.com", 1234)),
identitiesAndPrivateKeys.map { it.first },
platformVersion,
serial

View File

@ -39,7 +39,6 @@ dependencies {
// Jackson support: serialisation to/from JSON, YAML, etc
compile project(':client:jackson')
compile group: 'org.json', name: 'json', version: json_version
// JOpt: for command line flags.

View File

@ -1,10 +1,7 @@
package net.corda.tools.shell
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import net.corda.client.jackson.JacksonSupport
@ -17,13 +14,10 @@ import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.tools.shell.utlities.ANSIProgressRenderer
import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer
import org.crsh.command.InvocationContext
@ -45,7 +39,6 @@ import org.crsh.util.Utils
import org.crsh.vfs.FS
import org.crsh.vfs.spi.file.FileMountFactory
import org.crsh.vfs.spi.url.ClassPathMountFactory
import org.json.JSONObject
import org.slf4j.LoggerFactory
import rx.Observable
import rx.Subscriber
@ -187,43 +180,22 @@ object InteractiveShell {
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
// serializers.
return JacksonSupport.createDefaultMapper(rpcOps, YAMLFactory(), true).apply {
val rpcModule = SimpleModule()
rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer)
rpcModule.addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer)
rpcModule.addDeserializer(UUID::class.java, UUIDDeserializer)
val rpcModule = SimpleModule().apply {
addDeserializer(InputStream::class.java, InputStreamDeserializer)
addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer)
}
registerModule(rpcModule)
}
}
private object NodeInfoSerializer : JsonSerializer<NodeInfo>() {
override fun serialize(nodeInfo: NodeInfo, gen: JsonGenerator, serializers: SerializerProvider) {
val json = JSONObject()
json["addresses"] = nodeInfo.addresses.map { address -> address.serialise() }
json["legalIdentities"] = nodeInfo.legalIdentities.map { address -> address.serialise() }
json["platformVersion"] = nodeInfo.platformVersion
json["serial"] = nodeInfo.serial
gen.writeRaw(json.toString())
}
private fun NetworkHostAndPort.serialise() = this.toString()
private fun Party.serialise() = JSONObject().put("name", this.name)
private operator fun JSONObject.set(key: String, value: Any?): JSONObject {
return put(key, value)
}
}
private fun createOutputMapper(): ObjectMapper {
return JacksonSupport.createNonRpcMapper().apply {
// Register serializers for stateful objects from libraries that are special to the RPC system and don't
// make sense to print out to the screen. For classes we own, annotations can be used instead.
val rpcModule = SimpleModule()
rpcModule.addSerializer(Observable::class.java, ObservableSerializer)
rpcModule.addSerializer(InputStream::class.java, InputStreamSerializer)
rpcModule.addSerializer(NodeInfo::class.java, NodeInfoSerializer)
val rpcModule = SimpleModule().apply {
addSerializer(Observable::class.java, ObservableSerializer)
addSerializer(InputStream::class.java, InputStreamSerializer)
}
registerModule(rpcModule)
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
@ -240,7 +212,12 @@ object InteractiveShell {
* the [runFlowFromString] method and starts the requested flow. Ctrl-C can be used to cancel.
*/
@JvmStatic
fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer, om: ObjectMapper) {
fun runFlowByNameFragment(nameFragment: String,
inputData: String,
output: RenderPrintWriter,
rpcOps: CordaRPCOps,
ansiProgressRenderer: ANSIProgressRenderer,
om: ObjectMapper) {
val matches = try {
rpcOps.registeredFlows().filter { nameFragment in it }
} catch (e: PermissionException) {

View File

@ -50,16 +50,6 @@ object UniqueIdentifierDeserializer : JsonDeserializer<UniqueIdentifier>() {
}
}
/**
* String value deserialized to [UUID].
* */
object UUIDDeserializer : JsonDeserializer<UUID>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UUID {
//Create UUID object from string.
return UUID.fromString(p.text)
}
}
// An InputStream found in a response triggers a request to the user to provide somewhere to save it.
object InputStreamSerializer : JsonSerializer<InputStream>() {
var invokeContext: InvocationContext<*>? = null

View File

@ -28,7 +28,6 @@ class CustomTypeJsonParsingTests {
objectMapper = ObjectMapper()
val simpleModule = SimpleModule()
simpleModule.addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer)
simpleModule.addDeserializer(UUID::class.java, UUIDDeserializer)
objectMapper.registerModule(simpleModule)
}