mirror of
https://github.com/corda/corda.git
synced 2025-01-30 16:14:39 +00:00
CORDA-1709 - The MVP blob inspector, able to inspect network service blobs (#3503)
* 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. (cherry picked from commit 3bb95c3) * CORDA-1238: Updated JacksonSupport to support SerializedBytes, CertPath, X509Certificate and the signature classes (#3145) SerializedBytes are first converted to the object it represents before being serialised as a pojo. These changes will be needed to support the the blob inspector when it will output to YAML/JSON. (cherry picked from commit b031e66) * Cherry picked part of commit 824adca to port over *only* the JackSupport refactoring. * CORDA-1238: Moved the blob inspector out of experimental and wired it to JackonSupport (#3224) The existing output format was not complete and so was deleted to avoid it becoming a tech debt. We can always resurrect it at a later point. (cherry picked from commit 4e0378d) * Added back support for parsing OpaqueBytes as UTF-8 strings in JacksonSupport (#3240) (cherry picked from commit d772bc8) * Cleaned up blob inspector doc (#3284) (cherry picked from commit b7fbebb) * Blobinspector: trace level logging with --verbose (#3313) (cherry picked from commit 6a2e50b) * Cherry picked part of commit 3046843 to fix issue with --version * Fixes to the api file
This commit is contained in:
parent
ba37b3ea57
commit
c93c661190
@ -3707,6 +3707,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 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 net.corda.client.jackson.JacksonSupport$CordaX500NameSerializer INSTANCE
|
||||||
##
|
##
|
||||||
|
@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 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 <init>(net.corda.core.node.services.IdentityService, com.fasterxml.jackson.core.JsonFactory, boolean)
|
||||||
public final boolean getFuzzyIdentityMatch()
|
public final boolean getFuzzyIdentityMatch()
|
||||||
@ -3715,6 +3716,7 @@ 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 partyFromKey(java.security.PublicKey)
|
||||||
@org.jetbrains.annotations.Nullable public net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name)
|
@org.jetbrains.annotations.Nullable public net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name)
|
||||||
##
|
##
|
||||||
|
@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 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)
|
public <init>(com.fasterxml.jackson.core.JsonFactory)
|
||||||
@org.jetbrains.annotations.NotNull public Void partiesFromName(String)
|
@org.jetbrains.annotations.NotNull public Void partiesFromName(String)
|
||||||
@ -3741,6 +3743,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)
|
@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 final net.corda.client.jackson.JacksonSupport$PartyDeserializer INSTANCE
|
||||||
##
|
##
|
||||||
|
@DoNotImplement
|
||||||
public static interface net.corda.client.jackson.JacksonSupport$PartyObjectMapper
|
public static interface net.corda.client.jackson.JacksonSupport$PartyObjectMapper
|
||||||
@org.jetbrains.annotations.NotNull public abstract Set partiesFromName(String)
|
@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 partyFromKey(java.security.PublicKey)
|
||||||
@ -3758,6 +3761,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 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 net.corda.client.jackson.JacksonSupport$PublicKeySerializer INSTANCE
|
||||||
##
|
##
|
||||||
|
@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 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 <init>(net.corda.core.messaging.CordaRPCOps, com.fasterxml.jackson.core.JsonFactory, boolean)
|
||||||
public final boolean getFuzzyIdentityMatch()
|
public final boolean getFuzzyIdentityMatch()
|
||||||
|
4
.idea/compiler.xml
generated
4
.idea/compiler.xml
generated
@ -10,6 +10,8 @@
|
|||||||
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_main" target="1.8" />
|
<module name="bank-of-corda-demo_main" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_test" target="1.8" />
|
<module name="bank-of-corda-demo_test" target="1.8" />
|
||||||
|
<module name="blobinspector_main" target="1.8" />
|
||||||
|
<module name="blobinspector_test" target="1.8" />
|
||||||
<module name="bootstrapper_main" target="1.8" />
|
<module name="bootstrapper_main" target="1.8" />
|
||||||
<module name="bootstrapper_test" target="1.8" />
|
<module name="bootstrapper_test" target="1.8" />
|
||||||
<module name="buildSrc_main" target="1.8" />
|
<module name="buildSrc_main" target="1.8" />
|
||||||
@ -140,6 +142,8 @@
|
|||||||
<module name="testing-test-common_test" target="1.8" />
|
<module name="testing-test-common_test" target="1.8" />
|
||||||
<module name="testing-test-utils_main" target="1.8" />
|
<module name="testing-test-utils_main" target="1.8" />
|
||||||
<module name="testing-test-utils_test" target="1.8" />
|
<module name="testing-test-utils_test" target="1.8" />
|
||||||
|
<module name="tools-blobinspector_main" target="1.8" />
|
||||||
|
<module name="tools-blobinspector_test" target="1.8" />
|
||||||
<module name="tools_main" target="1.8" />
|
<module name="tools_main" target="1.8" />
|
||||||
<module name="tools_test" target="1.8" />
|
<module name="tools_test" target="1.8" />
|
||||||
<module name="trader-demo_integrationTest" target="1.8" />
|
<module name="trader-demo_integrationTest" target="1.8" />
|
||||||
|
@ -40,7 +40,6 @@ buildscript {
|
|||||||
ext.jetty_version = '9.4.7.v20170914'
|
ext.jetty_version = '9.4.7.v20170914'
|
||||||
ext.jersey_version = '2.25'
|
ext.jersey_version = '2.25'
|
||||||
ext.jolokia_version = '1.3.7'
|
ext.jolokia_version = '1.3.7'
|
||||||
ext.json_version = '20180130'
|
|
||||||
ext.assertj_version = '3.8.0'
|
ext.assertj_version = '3.8.0'
|
||||||
ext.slf4j_version = '1.7.25'
|
ext.slf4j_version = '1.7.25'
|
||||||
ext.log4j_version = '2.9.1'
|
ext.log4j_version = '2.9.1'
|
||||||
@ -71,6 +70,7 @@ buildscript {
|
|||||||
ext.docker_compose_rule_version = '0.33.1'
|
ext.docker_compose_rule_version = '0.33.1'
|
||||||
ext.selenium_version = '3.8.1'
|
ext.selenium_version = '3.8.1'
|
||||||
ext.ghostdriver_version = '2.1.0'
|
ext.ghostdriver_version = '2.1.0'
|
||||||
|
ext.jcabi_manifests_version = '1.1'
|
||||||
|
|
||||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||||
ext.java8_minUpdateVersion = '131'
|
ext.java8_minUpdateVersion = '131'
|
||||||
|
@ -6,11 +6,8 @@ apply plugin: 'com.jfrog.artifactory'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
testCompile project(':test-utils')
|
|
||||||
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
|
||||||
|
|
||||||
// Jackson and its plugins: parsing to/from JSON and other textual formats.
|
// Jackson and its plugins: parsing to/from JSON and other textual formats.
|
||||||
compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}"
|
compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}"
|
||||||
// Yaml is useful for parsing strings to method calls.
|
// Yaml is useful for parsing strings to method calls.
|
||||||
@ -19,7 +16,9 @@ dependencies {
|
|||||||
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
|
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
|
||||||
compile "com.google.guava:guava:$guava_version"
|
compile "com.google.guava:guava:$guava_version"
|
||||||
|
|
||||||
|
testCompile project(':test-utils')
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,35 +4,48 @@ import com.fasterxml.jackson.annotation.JsonIgnore
|
|||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.core.*
|
import com.fasterxml.jackson.core.*
|
||||||
import com.fasterxml.jackson.databind.*
|
import com.fasterxml.jackson.databind.*
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
|
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import net.corda.client.jackson.internal.CordaModule
|
||||||
|
import net.corda.client.jackson.internal.ToStringSerialize
|
||||||
|
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.Amount
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.identity.*
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.*
|
import net.corda.core.transactions.CoreTransaction
|
||||||
|
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.base58ToByteArray
|
import net.corda.core.utilities.parsePublicKeyBase58
|
||||||
import net.corda.core.utilities.base64ToByteArray
|
import net.corda.core.utilities.toBase58String
|
||||||
import net.corda.core.utilities.toBase64
|
import org.bouncycastle.asn1.x509.KeyPurposeId
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
import java.lang.reflect.Modifier
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.CertPath
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
|
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
|
||||||
@ -40,91 +53,87 @@ import java.util.*
|
|||||||
*
|
*
|
||||||
* Note that Jackson can also be used to serialise/deserialise other formats such as Yaml and XML.
|
* Note that Jackson can also be used to serialise/deserialise other formats such as Yaml and XML.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
|
||||||
object JacksonSupport {
|
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)
|
// If you change this API please update the docs in the docsite (json.rst)
|
||||||
|
|
||||||
|
@DoNotImplement
|
||||||
interface PartyObjectMapper {
|
interface PartyObjectMapper {
|
||||||
|
val isFullParties: Boolean
|
||||||
fun wellKnownPartyFromX500Name(name: CordaX500Name): Party?
|
fun wellKnownPartyFromX500Name(name: CordaX500Name): Party?
|
||||||
fun partyFromKey(owningKey: PublicKey): Party?
|
fun partyFromKey(owningKey: PublicKey): Party?
|
||||||
fun partiesFromName(query: String): Set<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
|
||||||
|
@JvmOverloads constructor(val rpc: CordaRPCOps,
|
||||||
|
factory: JsonFactory,
|
||||||
|
val fuzzyIdentityMatch: Boolean,
|
||||||
|
override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) {
|
||||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = rpc.wellKnownPartyFromX500Name(name)
|
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = rpc.wellKnownPartyFromX500Name(name)
|
||||||
override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey)
|
override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey)
|
||||||
override fun partiesFromName(query: String) = rpc.partiesFromName(query, fuzzyIdentityMatch)
|
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
|
||||||
|
@JvmOverloads constructor(val identityService: IdentityService,
|
||||||
|
factory: JsonFactory,
|
||||||
|
val fuzzyIdentityMatch: Boolean,
|
||||||
|
override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) {
|
||||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = identityService.wellKnownPartyFromX500Name(name)
|
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = identityService.wellKnownPartyFromX500Name(name)
|
||||||
override fun partyFromKey(owningKey: PublicKey): Party? = identityService.partyFromKey(owningKey)
|
override fun partyFromKey(owningKey: PublicKey): Party? = identityService.partyFromKey(owningKey)
|
||||||
override fun partiesFromName(query: String) = identityService.partiesFromName(query, fuzzyIdentityMatch)
|
override fun partiesFromName(query: String) = identityService.partiesFromName(query, fuzzyIdentityMatch)
|
||||||
|
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoPartyObjectMapper(factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
|
@Deprecated("This is an internal class, do not use", replaceWith = ReplaceWith("JacksonSupport.createNonRpcMapper"))
|
||||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = throw UnsupportedOperationException()
|
class NoPartyObjectMapper
|
||||||
override fun partyFromKey(owningKey: PublicKey): Party? = throw UnsupportedOperationException()
|
@JvmOverloads constructor(factory: JsonFactory,
|
||||||
override fun partiesFromName(query: String) = throw UnsupportedOperationException()
|
override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) {
|
||||||
|
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 {
|
@Suppress("unused")
|
||||||
SimpleModule("core").apply {
|
@Deprecated("Do not use this as it's not thread safe. Instead get a ObjectMapper instance with one of the create*Mapper methods.")
|
||||||
addSerializer(AnonymousParty::class.java, AnonymousPartySerializer)
|
val cordaModule: Module by lazy(::CordaModule)
|
||||||
addDeserializer(AnonymousParty::class.java, AnonymousPartyDeserializer)
|
|
||||||
addSerializer(Party::class.java, PartySerializer)
|
|
||||||
addDeserializer(Party::class.java, 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)
|
|
||||||
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)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
setMixInAnnotation(SignedTransaction::class.java, SignedTransactionMixin::class.java)
|
|
||||||
setMixInAnnotation(WireTransaction::class.java, WireTransactionMixin::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names.
|
* Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names.
|
||||||
*
|
*
|
||||||
* If [fuzzyIdentityMatch] is false, fields mapped to [Party] objects must be in X.500 name form and precisely
|
* @param fuzzyIdentityMatch If false, fields mapped to [Party] objects must be in X.500 name form and precisely
|
||||||
* match an identity known from the network map. If true, the name is matched more leniently but if the match
|
* 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.
|
* is ambiguous a [JsonParseException] is thrown.
|
||||||
|
*
|
||||||
|
* @param fullParties If true then [Party] objects will be serialised as JSON objects, with the owning key serialised
|
||||||
|
* in addition to the name. For [PartyAndCertificate] objects the cert path will be included.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun createDefaultMapper(rpc: CordaRPCOps, factory: JsonFactory = JsonFactory(),
|
fun createDefaultMapper(rpc: CordaRPCOps,
|
||||||
fuzzyIdentityMatch: Boolean = false): ObjectMapper = configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch))
|
factory: JsonFactory = JsonFactory(),
|
||||||
|
fuzzyIdentityMatch: Boolean = false,
|
||||||
|
fullParties: Boolean = false): ObjectMapper {
|
||||||
|
return configureMapper(RpcObjectMapper(rpc, factory, fuzzyIdentityMatch, fullParties))
|
||||||
|
}
|
||||||
|
|
||||||
/** For testing or situations where deserialising parties is not required */
|
/**
|
||||||
|
* For testing or situations where deserialising parties is not required
|
||||||
|
*
|
||||||
|
* @param fullParties If true then [Party] objects will be serialised as JSON objects, with the owning key serialised
|
||||||
|
* in addition to the name. For [PartyAndCertificate] objects the cert path will be included.
|
||||||
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun createNonRpcMapper(factory: JsonFactory = JsonFactory()): ObjectMapper = configureMapper(NoPartyObjectMapper(factory))
|
fun createNonRpcMapper(factory: JsonFactory = JsonFactory(), fullParties: Boolean = false): ObjectMapper {
|
||||||
|
return configureMapper(NoPartyObjectMapper(factory, fullParties))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Jackson ObjectMapper that uses an [IdentityService] directly inside the node to deserialise parties from string names.
|
* Creates a Jackson ObjectMapper that uses an [IdentityService] directly inside the node to deserialise parties from string names.
|
||||||
@ -133,139 +142,238 @@ object JacksonSupport {
|
|||||||
* match an identity known from the network map. If true, the name is matched more leniently but if the match
|
* 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.
|
* is ambiguous a [JsonParseException] is thrown.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This is an internal method, do not use")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun createInMemoryMapper(identityService: IdentityService, factory: JsonFactory = JsonFactory(),
|
fun createInMemoryMapper(identityService: IdentityService,
|
||||||
fuzzyIdentityMatch: Boolean = false) = configureMapper(IdentityObjectMapper(identityService, factory, fuzzyIdentityMatch))
|
factory: JsonFactory = JsonFactory(),
|
||||||
|
fuzzyIdentityMatch: Boolean = false): ObjectMapper {
|
||||||
private fun configureMapper(mapper: ObjectMapper): ObjectMapper = mapper.apply {
|
return configureMapper(IdentityObjectMapper(identityService, factory, fuzzyIdentityMatch))
|
||||||
enable(SerializationFeature.INDENT_OUTPUT)
|
|
||||||
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
|
||||||
enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
|
|
||||||
|
|
||||||
registerModule(JavaTimeModule())
|
|
||||||
registerModule(cordaModule)
|
|
||||||
registerModule(KotlinModule())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object ToStringSerializer : JsonSerializer<Any>() {
|
@CordaInternal
|
||||||
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
|
@VisibleForTesting
|
||||||
generator.writeString(obj.toString())
|
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().apply {
|
||||||
|
addSerializer(Date::class.java, DateSerializer)
|
||||||
|
})
|
||||||
|
registerModule(CordaModule())
|
||||||
|
registerModule(KotlinModule())
|
||||||
|
|
||||||
|
addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java)
|
||||||
|
addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java)
|
||||||
|
addMixIn(X509Certificate::class.java, X509CertificateMixin::class.java)
|
||||||
|
addMixIn(CertPath::class.java, CertPathMixin::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ToStringSerialize
|
||||||
|
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
|
||||||
|
private interface BigDecimalMixin
|
||||||
|
|
||||||
|
private object DateSerializer : JsonSerializer<Date>() {
|
||||||
|
override fun serialize(value: Date, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
gen.writeObject(value.toInstant())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToStringSerialize
|
||||||
|
private interface X500PrincipalMixin
|
||||||
|
|
||||||
|
@JsonSerialize(using = X509CertificateSerializer::class)
|
||||||
|
@JsonDeserialize(using = X509CertificateDeserializer::class)
|
||||||
|
private interface X509CertificateMixin
|
||||||
|
|
||||||
|
private object X509CertificateSerializer : JsonSerializer<X509Certificate>() {
|
||||||
|
val keyUsages = arrayOf(
|
||||||
|
"digitalSignature",
|
||||||
|
"nonRepudiation",
|
||||||
|
"keyEncipherment",
|
||||||
|
"dataEncipherment",
|
||||||
|
"keyAgreement",
|
||||||
|
"keyCertSign",
|
||||||
|
"cRLSign",
|
||||||
|
"encipherOnly",
|
||||||
|
"decipherOnly"
|
||||||
|
)
|
||||||
|
|
||||||
|
val keyPurposeIds = KeyPurposeId::class.java
|
||||||
|
.fields
|
||||||
|
.filter { Modifier.isStatic(it.modifiers) && it.type == KeyPurposeId::class.java }
|
||||||
|
.associateBy({ (it.get(null) as KeyPurposeId).id }, { it.name })
|
||||||
|
|
||||||
|
val knownExtensions = setOf(
|
||||||
|
"2.5.29.15",
|
||||||
|
"2.5.29.17",
|
||||||
|
"2.5.29.18",
|
||||||
|
"2.5.29.19",
|
||||||
|
"2.5.29.37",
|
||||||
|
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("cordaCertRole", CertRole.extract(value))
|
||||||
|
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") {
|
||||||
|
val isCa = value.basicConstraints != -1
|
||||||
|
writeBooleanField("isCA", isCa)
|
||||||
|
if (isCa) {
|
||||||
|
writeObjectField("pathLength", value.basicConstraints.let { if (it != Int.MAX_VALUE) it else null })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeObjectField("subjectAlternativeNames", value.subjectAlternativeNames)
|
||||||
|
writeObjectField("issuerAlternativeNames", value.issuerAlternativeNames)
|
||||||
|
writeObjectField("otherCriticalExtensions", value.criticalExtensionOIDs - knownExtensions)
|
||||||
|
writeObjectField("otherNonCriticalExtensions", value.nonCriticalExtensionOIDs - knownExtensions)
|
||||||
|
writeBinaryField("encoded", value.encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class X509CertificateDeserializer : JsonDeserializer<X509Certificate>() {
|
||||||
|
private val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): X509Certificate {
|
||||||
|
val encoded = if (parser.currentToken == JsonToken.START_OBJECT) {
|
||||||
|
parser.readValueAsTree<ObjectNode>()["encoded"].binaryValue()
|
||||||
|
} else {
|
||||||
|
parser.binaryValue
|
||||||
|
}
|
||||||
|
return certFactory.generateCertificate(encoded.inputStream()) as X509Certificate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerialize(using = CertPathSerializer::class)
|
||||||
|
@JsonDeserialize(using = CertPathDeserializer::class)
|
||||||
|
private interface CertPathMixin
|
||||||
|
|
||||||
|
private class CertPathSerializer : JsonSerializer<CertPath>() {
|
||||||
|
override fun serialize(value: CertPath, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
gen.writeObject(CertPathWrapper(value.type, uncheckedCast(value.certificates)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CertPathDeserializer : JsonDeserializer<CertPath>() {
|
||||||
|
private val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): CertPath {
|
||||||
|
val wrapper = parser.readValueAs<CertPathWrapper>()
|
||||||
|
return certFactory.generateCertPath(wrapper.certificates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class CertPathWrapper(val type: String, val certificates: List<X509Certificate>) {
|
||||||
|
init {
|
||||||
|
require(type == "X.509") { "Only X.509 cert paths are supported" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
|
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
|
||||||
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
|
override fun serialize(value: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
PublicKeySerializer.serialize(obj.owningKey, generator, provider)
|
generator.writeObject(value.owningKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object AnonymousPartyDeserializer : JsonDeserializer<AnonymousParty>() {
|
object AnonymousPartyDeserializer : JsonDeserializer<AnonymousParty>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): AnonymousParty {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): AnonymousParty {
|
||||||
if (parser.currentToken == JsonToken.FIELD_NAME) {
|
return AnonymousParty(parser.readValueAs(PublicKey::class.java))
|
||||||
parser.nextToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
val key = PublicKeyDeserializer.deserialize(parser, context)
|
|
||||||
return AnonymousParty(key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object PartySerializer : JsonSerializer<Party>() {
|
object PartySerializer : JsonSerializer<Party>() {
|
||||||
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
|
override fun serialize(value: Party, gen: JsonGenerator, provider: SerializerProvider) {
|
||||||
generator.writeString(obj.name.toString())
|
val mapper = gen.codec as PartyObjectMapper
|
||||||
|
if (mapper.isFullParties) {
|
||||||
|
gen.writeObject(PartyAnalogue(value.name, value.owningKey))
|
||||||
|
} else {
|
||||||
|
gen.writeObject(value.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object PartyDeserializer : JsonDeserializer<Party>() {
|
object PartyDeserializer : JsonDeserializer<Party>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): Party {
|
||||||
if (parser.currentToken == JsonToken.FIELD_NAME) {
|
|
||||||
parser.nextToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
val mapper = parser.codec as PartyObjectMapper
|
val mapper = parser.codec as PartyObjectMapper
|
||||||
// The comma character is invalid in base64, and required as a separator for X.500 names. As Corda
|
return if (parser.currentToken == JsonToken.START_OBJECT) {
|
||||||
// X.500 names all involve at least three attributes (organisation, locality, country), they must
|
val analogue = parser.readValueAs<PartyAnalogue>()
|
||||||
// include a comma. As such we can use it as a distinguisher between the two types.
|
Party(analogue.name, analogue.owningKey)
|
||||||
return if (parser.text.contains(",")) {
|
|
||||||
val principal = CordaX500Name.parse(parser.text)
|
|
||||||
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
|
|
||||||
} else {
|
} else {
|
||||||
val nameMatches = mapper.partiesFromName(parser.text)
|
// The comma character is invalid in Base58, and required as a separator for X.500 names. As Corda
|
||||||
if (nameMatches.isEmpty()) {
|
// X.500 names all involve at least three attributes (organisation, locality, country), they must
|
||||||
val derBytes = try {
|
// include a comma. As such we can use it as a distinguisher between the two types.
|
||||||
parser.text.base64ToByteArray()
|
if ("," in parser.text) {
|
||||||
} catch (e: AddressFormatException) {
|
val principal = CordaX500Name.parse(parser.text)
|
||||||
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a base64 encoded public key: " + e.message)
|
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
|
||||||
}
|
|
||||||
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()}")
|
|
||||||
} else if (nameMatches.size == 1) {
|
|
||||||
nameMatches.first()
|
|
||||||
} else {
|
} else {
|
||||||
throw JsonParseException(parser, "Ambiguous name match '${parser.text}': could be any of " + nameMatches.map { it.name }.joinToString(" ... or ..."))
|
lookupByNameSegment(mapper, parser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() {
|
private fun lookupByNameSegment(mapper: PartyObjectMapper, parser: JsonParser): Party {
|
||||||
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
|
val nameMatches = mapper.partiesFromName(parser.text)
|
||||||
generator.writeString(obj.toString())
|
return when {
|
||||||
|
nameMatches.isEmpty() -> {
|
||||||
|
val publicKey = parser.readValueAs<PublicKey>()
|
||||||
|
mapper.partyFromKey(publicKey)
|
||||||
|
?: throw JsonParseException(parser, "Could not find a Party with key ${publicKey.toStringShort()}")
|
||||||
|
}
|
||||||
|
nameMatches.size == 1 -> nameMatches.first()
|
||||||
|
else -> throw JsonParseException(parser, "Ambiguous name match '${parser.text}': could be any of " +
|
||||||
|
nameMatches.map { it.name }.joinToString(" ... or ... "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PartyAnalogue(val name: CordaX500Name, val owningKey: PublicKey)
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
|
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name {
|
||||||
if (parser.currentToken == JsonToken.FIELD_NAME) {
|
|
||||||
parser.nextToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
CordaX500Name.parse(parser.text)
|
CordaX500Name.parse(parser.text)
|
||||||
} catch (ex: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${ex.message}", ex)
|
throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object NodeInfoSerializer : JsonSerializer<NodeInfo>() {
|
@Deprecated("This is an internal class, do not use")
|
||||||
override fun serialize(value: NodeInfo, gen: JsonGenerator, serializers: SerializerProvider) {
|
|
||||||
gen.writeString(Base58.encode(value.serialize().bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
|
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): NodeInfo {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): NodeInfo {
|
||||||
if (parser.currentToken == JsonToken.FIELD_NAME) {
|
val mapper = parser.codec as PartyObjectMapper
|
||||||
parser.nextToken()
|
val party = parser.readValueAs<AbstractParty>()
|
||||||
}
|
return mapper.nodeInfoFromParty(party) ?: throw JsonParseException(parser, "Cannot find node with $party")
|
||||||
try {
|
|
||||||
return Base58.decode(parser.text).deserialize<NodeInfo>()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw JsonParseException(parser, "Invalid NodeInfo ${parser.text}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object SecureHashSerializer : JsonSerializer<SecureHash>() {
|
@Deprecated("This is an internal class, do not use")
|
||||||
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
|
|
||||||
generator.writeString(obj.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implemented as a class so that we can instantiate for T.
|
|
||||||
*/
|
|
||||||
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
|
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
|
||||||
if (parser.currentToken == JsonToken.FIELD_NAME) {
|
|
||||||
parser.nextToken()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return uncheckedCast(SecureHash.parse(parser.text))
|
return uncheckedCast(SecureHash.parse(parser.text))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -274,61 +382,95 @@ object JacksonSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object PublicKeySerializer : JsonSerializer<PublicKey>() {
|
object PublicKeySerializer : JsonSerializer<PublicKey>() {
|
||||||
override fun serialize(obj: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
generator.writeString(obj.encoded.toBase64())
|
generator.writeString(value.toBase58String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
|
object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): PublicKey {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): PublicKey {
|
||||||
return try {
|
return try {
|
||||||
val derBytes = parser.text.base64ToByteArray()
|
parsePublicKeyBase58(parser.text)
|
||||||
Crypto.decodePublicKey(derBytes)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}")
|
throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
val wrapper = parser.readValueAs<CurrencyAmountWrapper>()
|
||||||
|
Amount(wrapper.quantity, wrapper.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class CurrencyAmountWrapper(val quantity: Long, val token: Currency)
|
||||||
|
|
||||||
|
@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(UTF_8) ?: parser.binaryValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Everything below this point is no longer used but can't be deleted as they leaked into the public API
|
||||||
|
//
|
||||||
|
|
||||||
|
@Deprecated("No longer used as jackson already has a toString serializer",
|
||||||
|
replaceWith = ReplaceWith("com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance"))
|
||||||
|
object ToStringSerializer : JsonSerializer<Any>() {
|
||||||
|
override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
|
generator.writeString(obj.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
|
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() {
|
||||||
|
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
|
generator.writeString(obj.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
|
object NodeInfoSerializer : JsonSerializer<NodeInfo>() {
|
||||||
|
override fun serialize(value: NodeInfo, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
gen.writeString(Base58.encode(value.serialize().bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
|
object SecureHashSerializer : JsonSerializer<SecureHash>() {
|
||||||
|
override fun serialize(obj: SecureHash, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
|
generator.writeString(obj.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
object AmountSerializer : JsonSerializer<Amount<*>>() {
|
object AmountSerializer : JsonSerializer<Amount<*>>() {
|
||||||
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
gen.writeString(value.toString())
|
gen.writeString(value.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
|
@Deprecated("This is an internal class, do not use")
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
|
|
||||||
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()
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() {
|
|
||||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): OpaqueBytes {
|
|
||||||
return OpaqueBytes(parser.text.toByteArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() {
|
object OpaqueBytesSerializer : JsonSerializer<OpaqueBytes>() {
|
||||||
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
|
override fun serialize(value: OpaqueBytes, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
gen.writeBinary(value.bytes)
|
gen.writeBinary(value.bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
|
@Suppress("unused")
|
||||||
abstract class SignedTransactionMixin {
|
abstract class SignedTransactionMixin {
|
||||||
@JsonIgnore abstract fun getTxBits(): SerializedBytes<CoreTransaction>
|
@JsonIgnore abstract fun getTxBits(): SerializedBytes<CoreTransaction>
|
||||||
@JsonProperty("signatures") protected abstract fun getSigs(): List<TransactionSignature>
|
@JsonProperty("signatures") protected abstract fun getSigs(): List<TransactionSignature>
|
||||||
@ -341,6 +483,8 @@ object JacksonSupport {
|
|||||||
@JsonIgnore abstract fun getRequiredSigningKeys(): Set<PublicKey>
|
@JsonIgnore abstract fun getRequiredSigningKeys(): Set<PublicKey>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This is an internal class, do not use")
|
||||||
|
@Suppress("unused")
|
||||||
abstract class WireTransactionMixin {
|
abstract class WireTransactionMixin {
|
||||||
@JsonIgnore abstract fun getMerkleTree(): MerkleTree
|
@JsonIgnore abstract fun getMerkleTree(): MerkleTree
|
||||||
@JsonIgnore abstract fun getAvailableComponents(): List<Any>
|
@JsonIgnore abstract fun getAvailableComponents(): List<Any>
|
||||||
@ -348,4 +492,3 @@ object JacksonSupport {
|
|||||||
@JsonIgnore abstract fun getOutputStates(): List<ContractState>
|
@JsonIgnore abstract fun getOutputStates(): List<ContractState>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package net.corda.client.jackson.internal
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.core.JsonToken
|
||||||
|
import com.fasterxml.jackson.databind.*
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||||
|
import net.corda.client.jackson.JacksonSupport
|
||||||
|
import net.corda.core.contracts.Amount
|
||||||
|
import net.corda.core.crypto.DigitalSignature
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.TransactionSignature
|
||||||
|
import net.corda.core.identity.*
|
||||||
|
import net.corda.core.internal.DigitalSignatureWithCert
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
|
import net.corda.core.utilities.ByteSequence
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.CertPath
|
||||||
|
|
||||||
|
class CordaModule : SimpleModule("corda-core") {
|
||||||
|
override fun setupModule(context: SetupContext) {
|
||||||
|
super.setupModule(context)
|
||||||
|
|
||||||
|
context.setMixInAnnotations(PartyAndCertificate::class.java, PartyAndCertificateMixin::class.java)
|
||||||
|
context.setMixInAnnotations(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java)
|
||||||
|
context.setMixInAnnotations(CordaX500Name::class.java, CordaX500NameMixin::class.java)
|
||||||
|
context.setMixInAnnotations(Amount::class.java, AmountMixin::class.java)
|
||||||
|
context.setMixInAnnotations(AbstractParty::class.java, AbstractPartyMixin::class.java)
|
||||||
|
context.setMixInAnnotations(AnonymousParty::class.java, AnonymousPartyMixin::class.java)
|
||||||
|
context.setMixInAnnotations(Party::class.java, PartyMixin::class.java)
|
||||||
|
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
|
||||||
|
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
|
||||||
|
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
|
||||||
|
context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java)
|
||||||
|
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||||
|
context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||||
|
context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||||
|
context.setMixInAnnotations(SignedTransaction::class.java, JacksonSupport.SignedTransactionMixin::class.java)
|
||||||
|
context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java)
|
||||||
|
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToStringSerialize
|
||||||
|
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
|
||||||
|
private interface NetworkHostAndPortMixin
|
||||||
|
|
||||||
|
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
|
||||||
|
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
|
||||||
|
return NetworkHostAndPort.parse(parser.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerialize(using = PartyAndCertificateSerializer::class)
|
||||||
|
// TODO Add deserialization which follows the same lookup logic as Party
|
||||||
|
private interface PartyAndCertificateMixin
|
||||||
|
|
||||||
|
private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
|
||||||
|
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
val mapper = gen.codec as JacksonSupport.PartyObjectMapper
|
||||||
|
if (mapper.isFullParties) {
|
||||||
|
gen.writeObject(PartyAndCertificateWrapper(value.name, value.certPath))
|
||||||
|
} else {
|
||||||
|
gen.writeObject(value.party)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PartyAndCertificateWrapper(val name: CordaX500Name, val certPath: CertPath)
|
||||||
|
|
||||||
|
@JsonSerialize(using = SerializedBytesSerializer::class)
|
||||||
|
@JsonDeserialize(using = SerializedBytesDeserializer::class)
|
||||||
|
private class SerializedBytesMixin
|
||||||
|
|
||||||
|
private class SerializedBytesSerializer : JsonSerializer<SerializedBytes<*>>() {
|
||||||
|
override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
val deserialized = value.deserialize<Any>()
|
||||||
|
gen.jsonObject {
|
||||||
|
writeStringField("class", deserialized.javaClass.name)
|
||||||
|
writeObjectField("deserialized", deserialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SerializedBytesDeserializer : JsonDeserializer<SerializedBytes<*>>() {
|
||||||
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes<Any> {
|
||||||
|
return if (parser.currentToken == JsonToken.START_OBJECT) {
|
||||||
|
val mapper = parser.codec as ObjectMapper
|
||||||
|
val json = parser.readValueAsTree<ObjectNode>()
|
||||||
|
val clazz = context.findClass(json["class"].textValue())
|
||||||
|
val pojo = mapper.convertValue(json["deserialized"], clazz)
|
||||||
|
pojo.serialize()
|
||||||
|
} else {
|
||||||
|
SerializedBytes(parser.binaryValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonDeserialize(using = JacksonSupport.PartyDeserializer::class)
|
||||||
|
private interface AbstractPartyMixin
|
||||||
|
|
||||||
|
@JsonSerialize(using = JacksonSupport.AnonymousPartySerializer::class)
|
||||||
|
@JsonDeserialize(using = JacksonSupport.AnonymousPartyDeserializer::class)
|
||||||
|
private interface AnonymousPartyMixin
|
||||||
|
|
||||||
|
@JsonSerialize(using = JacksonSupport.PartySerializer::class)
|
||||||
|
private interface PartyMixin
|
||||||
|
|
||||||
|
@ToStringSerialize
|
||||||
|
@JsonDeserialize(using = JacksonSupport.CordaX500NameDeserializer::class)
|
||||||
|
private interface CordaX500NameMixin
|
||||||
|
|
||||||
|
@JsonIgnoreProperties("legalIdentities") // This is already covered by legalIdentitiesAndCerts
|
||||||
|
@JsonDeserialize(using = JacksonSupport.NodeInfoDeserializer::class)
|
||||||
|
private interface NodeInfoMixin
|
||||||
|
|
||||||
|
@ToStringSerialize
|
||||||
|
@JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class)
|
||||||
|
private interface SecureHashSHA256Mixin
|
||||||
|
|
||||||
|
@JsonSerialize(using = JacksonSupport.PublicKeySerializer::class)
|
||||||
|
@JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::class)
|
||||||
|
private interface PublicKeyMixin
|
||||||
|
|
||||||
|
@ToStringSerialize
|
||||||
|
@JsonDeserialize(using = JacksonSupport.AmountDeserializer::class)
|
||||||
|
private interface AmountMixin
|
||||||
|
|
||||||
|
@JsonDeserialize(using = JacksonSupport.OpaqueBytesDeserializer::class)
|
||||||
|
@JsonSerialize(using = ByteSequenceSerializer::class)
|
||||||
|
private interface ByteSequenceMixin
|
||||||
|
|
||||||
|
private class ByteSequenceSerializer : JsonSerializer<ByteSequence>() {
|
||||||
|
override fun serialize(value: ByteSequence, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
|
val bytes = value.bytes
|
||||||
|
gen.writeBinary(bytes, value.offset, value.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties("offset", "size")
|
||||||
|
@JsonSerialize
|
||||||
|
@JsonDeserialize
|
||||||
|
private interface ByteSequenceWithPropertiesMixin
|
@ -0,0 +1,25 @@
|
|||||||
|
package net.corda.client.jackson.internal
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
|
||||||
|
import com.fasterxml.jackson.module.kotlin.convertValue
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
inline fun <reified T : Any> JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
|
||||||
|
|
||||||
|
@JacksonAnnotationsInside
|
||||||
|
@JsonSerialize(using = ToStringSerializer::class)
|
||||||
|
annotation class ToStringSerialize
|
@ -1,42 +1,73 @@
|
|||||||
package net.corda.client.jackson
|
package net.corda.client.jackson
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
import com.fasterxml.jackson.core.JsonFactory
|
||||||
|
import com.fasterxml.jackson.databind.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.doReturn
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
|
import net.corda.client.jackson.internal.valueAs
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.crypto.CompositeKey
|
||||||
|
import net.corda.core.identity.*
|
||||||
|
import net.corda.core.internal.DigitalSignatureWithCert
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.utilities.*
|
||||||
import net.corda.finance.USD
|
import net.corda.finance.USD
|
||||||
|
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
import net.corda.testing.internal.createNodeInfoAndSigned
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
|
||||||
import net.corda.testing.core.TestIdentity
|
|
||||||
import net.corda.testing.internal.rigorousMock
|
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.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
|
import org.junit.runners.Parameterized.Parameters
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
import java.nio.charset.StandardCharsets.*
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.CertPath
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import javax.security.auth.x500.X500Principal
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class JacksonSupportTest {
|
@RunWith(Parameterized::class)
|
||||||
|
class JacksonSupportTest(@Suppress("unused") private val name: String, factory: JsonFactory) {
|
||||||
private companion object {
|
private companion object {
|
||||||
val SEED = BigInteger.valueOf(20170922L)!!
|
val SEED: BigInteger = BigInteger.valueOf(20170922L)
|
||||||
val mapper = JacksonSupport.createNonRpcMapper()
|
|
||||||
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
|
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
|
||||||
|
val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey
|
||||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||||
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
|
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||||
|
|
||||||
|
@Parameters(name = "{0}")
|
||||||
|
@JvmStatic
|
||||||
|
fun factories() = arrayOf(arrayOf("JSON", JsonFactory()), arrayOf("YAML", YAMLFactory()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
|
|
||||||
|
private val partyObjectMapper = TestPartyObjectMapper()
|
||||||
|
private val mapper = JacksonSupport.createPartyObjectMapper(partyObjectMapper, factory)
|
||||||
|
|
||||||
private lateinit var services: ServiceHub
|
private lateinit var services: ServiceHub
|
||||||
private lateinit var cordappProvider: CordappProvider
|
private lateinit var cordappProvider: CordappProvider
|
||||||
|
|
||||||
@ -48,54 +79,19 @@ class JacksonSupportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should serialize Composite keys`() {
|
fun `Amount(Currency) serialization`() {
|
||||||
val expected = "\"MIHAMBUGE2mtoq+J1bjir/ONk6yd5pab0FoDgaYAMIGiAgECMIGcMDIDLQAwKjAFBgMrZXADIQAgIX1QlJRgaLlD0ttLlJF5kNqT/7P7QwCvrWc9+/248gIBATAyAy0AMCowBQYDK2VwAyEAqS0JPGlzdviBZjB9FaNY+w6cVs3/CQ2A5EimE9Lyng4CAQEwMgMtADAqMAUGAytlcAMhALq4GG0gBQZIlaKE6ucooZsuoKUbH4MtGSmA6cwj136+AgEB\""
|
assertThat(mapper.valueToTree<TextNode>(Amount.parseCurrency("£25000000")).textValue()).isEqualTo("25000000.00 GBP")
|
||||||
val innerKeys = (1..3).map { i ->
|
assertThat(mapper.valueToTree<TextNode>(Amount.parseCurrency("$250000")).textValue()).isEqualTo("250000.00 USD")
|
||||||
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
|
@Test
|
||||||
fun readAmount() {
|
fun `Amount(Currency) deserialization`() {
|
||||||
val oldJson = """
|
val old = mapOf(
|
||||||
{
|
"quantity" to 2500000000,
|
||||||
"notional": {
|
"token" to "USD"
|
||||||
"quantity": 2500000000,
|
)
|
||||||
"token": "USD"
|
assertThat(mapper.convertValue<Amount<Currency>>(old)).isEqualTo(Amount(2_500_000_000, USD))
|
||||||
}
|
assertThat(mapper.convertValue<Amount<Currency>>(TextNode("$25000000"))).isEqualTo(Amount(2_500_000_000, USD))
|
||||||
}
|
|
||||||
"""
|
|
||||||
val newJson = """ { "notional" : "$25000000" } """
|
|
||||||
|
|
||||||
assertEquals(Amount(2500000000L, USD), mapper.readValue(newJson, Dummy::class.java).notional)
|
|
||||||
assertEquals(Amount(2500000000L, USD), mapper.readValue(oldJson, Dummy::class.java).notional)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun writeAmount() {
|
|
||||||
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT)
|
|
||||||
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$25000000"))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -119,6 +115,382 @@ class JacksonSupportTest {
|
|||||||
|
|
||||||
val writer = mapper.writer()
|
val writer = mapper.writer()
|
||||||
// We don't particularly care about the serialized format, just need to make sure it completes successfully.
|
// We don't particularly care about the serialized format, just need to make sure it completes successfully.
|
||||||
writer.writeValueAsString(makeDummyTx())
|
println(writer.writeValueAsString(makeDummyTx()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ByteSequence() {
|
||||||
|
val byteSequence: ByteSequence = OpaqueBytes.of(1, 2, 3, 4).subSequence(0, 2)
|
||||||
|
val json = mapper.valueToTree<BinaryNode>(byteSequence)
|
||||||
|
assertThat(json.binaryValue()).containsExactly(1, 2)
|
||||||
|
assertThat(json.asText()).isEqualTo(byteArrayOf(1, 2).toBase64())
|
||||||
|
assertThat(mapper.convertValue<ByteSequence>(json)).isEqualTo(byteSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `OpaqueBytes serialization`() {
|
||||||
|
val opaqueBytes = OpaqueBytes(secureRandomBytes(128))
|
||||||
|
val json = mapper.valueToTree<BinaryNode>(opaqueBytes)
|
||||||
|
assertThat(json.binaryValue()).isEqualTo(opaqueBytes.bytes)
|
||||||
|
assertThat(json.asText()).isEqualTo(opaqueBytes.bytes.toBase64())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `OpaqueBytes deserialization`() {
|
||||||
|
assertThat(mapper.convertValue<OpaqueBytes>(TextNode("1234"))).isEqualTo(OpaqueBytes("1234".toByteArray(UTF_8)))
|
||||||
|
assertThat(mapper.convertValue<OpaqueBytes>(BinaryNode(byteArrayOf(1, 2, 3, 4)))).isEqualTo(OpaqueBytes.of(1, 2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun SerializedBytes() {
|
||||||
|
val data = TestData(BOB_NAME, "Summary", SubTestData(1234))
|
||||||
|
val serializedBytes = data.serialize()
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(serializedBytes)
|
||||||
|
println(mapper.writeValueAsString(json))
|
||||||
|
assertThat(json["class"].textValue()).isEqualTo(TestData::class.java.name)
|
||||||
|
assertThat(json["deserialized"].valueAs<TestData>(mapper)).isEqualTo(data)
|
||||||
|
// Check that the entire JSON object can be converted back to the same SerializedBytes
|
||||||
|
assertThat(mapper.convertValue<SerializedBytes<*>>(json)).isEqualTo(serializedBytes)
|
||||||
|
assertThat(mapper.convertValue<SerializedBytes<*>>(BinaryNode(serializedBytes.bytes))).isEqualTo(serializedBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the class that was used to serialise the message for the test below. It's commented out so that it's no
|
||||||
|
// longer on the classpath.
|
||||||
|
// @CordaSerializable
|
||||||
|
// data class ClassNotOnClasspath(val name: CordaX500Name, val value: Int)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `SerializedBytes of class not on classpath`() {
|
||||||
|
// The contents of the file were written out as follows:
|
||||||
|
// ClassNotOnClasspath(BOB_NAME, 54321).serialize().open().copyTo("build" / "class-not-on-classpath-data")
|
||||||
|
|
||||||
|
val serializedBytes = SerializedBytes<Any>(javaClass.getResource("class-not-on-classpath-data").readBytes())
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(serializedBytes)
|
||||||
|
println(mapper.writeValueAsString(json))
|
||||||
|
assertThat(json["class"].textValue()).isEqualTo("net.corda.client.jackson.JacksonSupportTest\$ClassNotOnClasspath")
|
||||||
|
assertThat(json["deserialized"].valueAs<Map<*, *>>(mapper)).isEqualTo(mapOf(
|
||||||
|
"name" to BOB_NAME.toString(),
|
||||||
|
"value" to 54321
|
||||||
|
))
|
||||||
|
assertThat(mapper.convertValue<SerializedBytes<*>>(BinaryNode(serializedBytes.bytes))).isEqualTo(serializedBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun DigitalSignature() {
|
||||||
|
val digitalSignature = DigitalSignature(secureRandomBytes(128))
|
||||||
|
val json = mapper.valueToTree<BinaryNode>(digitalSignature)
|
||||||
|
assertThat(json.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||||
|
assertThat(json.asText()).isEqualTo(digitalSignature.bytes.toBase64())
|
||||||
|
assertThat(mapper.convertValue<DigitalSignature>(json)).isEqualTo(digitalSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `DigitalSignature WithKey`() {
|
||||||
|
val digitalSignature = DigitalSignature.WithKey(BOB_PUBKEY, secureRandomBytes(128))
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(digitalSignature)
|
||||||
|
val (by, bytes) = json.assertHasOnlyFields("by", "bytes")
|
||||||
|
assertThat(by.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
|
||||||
|
assertThat(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||||
|
assertThat(mapper.convertValue<DigitalSignature.WithKey>(json)).isEqualTo(digitalSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun DigitalSignatureWithCert() {
|
||||||
|
val digitalSignature = DigitalSignatureWithCert(MINI_CORP.identity.certificate, secureRandomBytes(128))
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(digitalSignature)
|
||||||
|
val (by, bytes) = json.assertHasOnlyFields("by", "bytes")
|
||||||
|
assertThat(by.valueAs<X509Certificate>(mapper)).isEqualTo(MINI_CORP.identity.certificate)
|
||||||
|
assertThat(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||||
|
assertThat(mapper.convertValue<DigitalSignatureWithCert>(json)).isEqualTo(digitalSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun TransactionSignature() {
|
||||||
|
val metadata = SignatureMetadata(1, 1)
|
||||||
|
val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, metadata)
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(transactionSignature)
|
||||||
|
val (bytes, by, signatureMetadata, partialMerkleTree) = json.assertHasOnlyFields(
|
||||||
|
"bytes",
|
||||||
|
"by",
|
||||||
|
"signatureMetadata",
|
||||||
|
"partialMerkleTree"
|
||||||
|
)
|
||||||
|
assertThat(bytes.binaryValue()).isEqualTo(transactionSignature.bytes)
|
||||||
|
assertThat(by.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
|
||||||
|
assertThat(signatureMetadata.valueAs<SignatureMetadata>(mapper)).isEqualTo(metadata)
|
||||||
|
assertThat(partialMerkleTree.isNull).isTrue()
|
||||||
|
assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 serialization with isFullParty = true`() {
|
||||||
|
partyObjectMapper.isFullParties = true
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(MINI_CORP.party)
|
||||||
|
val (name, owningKey) = json.assertHasOnlyFields("name", "owningKey")
|
||||||
|
assertThat(name.valueAs<CordaX500Name>(mapper)).isEqualTo(MINI_CORP.name)
|
||||||
|
assertThat(owningKey.valueAs<PublicKey>(mapper)).isEqualTo(MINI_CORP.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Party deserialization on full name`() {
|
||||||
|
fun convertToParty() = mapper.convertValue<Party>(TextNode(MINI_CORP.name.toString()))
|
||||||
|
|
||||||
|
// Check that it fails if it can't find the party
|
||||||
|
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))
|
||||||
|
|
||||||
|
// Check that it fails if it can't find the party
|
||||||
|
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()))
|
||||||
|
|
||||||
|
// Check that it fails if it can't find the party
|
||||||
|
assertThatThrownBy { convertToParty() }
|
||||||
|
|
||||||
|
partyObjectMapper.identities += MINI_CORP.party
|
||||||
|
assertThat(convertToParty()).isEqualTo(MINI_CORP.party)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Party deserialization on name and key`() {
|
||||||
|
val party = mapper.convertValue<Party>(mapOf(
|
||||||
|
"name" to MINI_CORP.name,
|
||||||
|
"owningKey" to MINI_CORP.publicKey
|
||||||
|
))
|
||||||
|
// Party.equals is only defined on the public key so we must check the name as well
|
||||||
|
assertThat(party.name).isEqualTo(MINI_CORP.name)
|
||||||
|
assertThat(party.owningKey).isEqualTo(MINI_CORP.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 + 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 json = mapper.valueToTree<TextNode>(publicKey)
|
||||||
|
assertThat(json.textValue()).isEqualTo(publicKey.toBase58String())
|
||||||
|
assertThat(mapper.convertValue<CompositeKey>(json)).isEqualTo(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun AnonymousParty() {
|
||||||
|
val anonymousParty = AnonymousParty(ALICE_PUBKEY)
|
||||||
|
val json = mapper.valueToTree<TextNode>(anonymousParty)
|
||||||
|
assertThat(json.textValue()).isEqualTo(ALICE_PUBKEY.toBase58String())
|
||||||
|
assertThat(mapper.convertValue<AnonymousParty>(json)).isEqualTo(anonymousParty)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `PartyAndCertificate serialization`() {
|
||||||
|
val json = mapper.valueToTree<TextNode>(MINI_CORP.identity)
|
||||||
|
assertThat(json.textValue()).isEqualTo(MINI_CORP.name.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `PartyAndCertificate serialization with isFullParty = true`() {
|
||||||
|
partyObjectMapper.isFullParties = true
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(MINI_CORP.identity)
|
||||||
|
println(mapper.writeValueAsString(json))
|
||||||
|
val (name, certPath) = json.assertHasOnlyFields("name", "certPath")
|
||||||
|
assertThat(name.valueAs<CordaX500Name>(mapper)).isEqualTo(MINI_CORP.name)
|
||||||
|
assertThat(certPath.valueAs<CertPath>(mapper)).isEqualTo(MINI_CORP.identity.certPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `PartyAndCertificate deserialization on cert path`() {
|
||||||
|
val certPathJson = mapper.valueToTree<JsonNode>(MINI_CORP.identity.certPath)
|
||||||
|
val partyAndCert = mapper.convertValue<PartyAndCertificate>(mapOf("certPath" to certPathJson))
|
||||||
|
// PartyAndCertificate.equals is defined on the Party so we must check the certPath directly
|
||||||
|
assertThat(partyAndCert.certPath).isEqualTo(MINI_CORP.identity.certPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `NodeInfo serialization`() {
|
||||||
|
val (nodeInfo) = createNodeInfoAndSigned(ALICE_NAME)
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(nodeInfo)
|
||||||
|
val (addresses, legalIdentitiesAndCerts, platformVersion, serial) = json.assertHasOnlyFields(
|
||||||
|
"addresses",
|
||||||
|
"legalIdentitiesAndCerts",
|
||||||
|
"platformVersion",
|
||||||
|
"serial"
|
||||||
|
)
|
||||||
|
addresses.run {
|
||||||
|
assertThat(this).hasSize(1)
|
||||||
|
assertThat(this[0].valueAs<NetworkHostAndPort>(mapper)).isEqualTo(nodeInfo.addresses[0])
|
||||||
|
}
|
||||||
|
legalIdentitiesAndCerts.run {
|
||||||
|
assertThat(this).hasSize(1)
|
||||||
|
assertThat(this[0].valueAs<CordaX500Name>(mapper)).isEqualTo(ALICE_NAME)
|
||||||
|
}
|
||||||
|
assertThat(platformVersion.intValue()).isEqualTo(nodeInfo.platformVersion)
|
||||||
|
assertThat(serial.longValue()).isEqualTo(nodeInfo.serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `NodeInfo deserialization 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 deserialization 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun CertPath() {
|
||||||
|
val certPath = MINI_CORP.identity.certPath
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(certPath)
|
||||||
|
println(mapper.writeValueAsString(json))
|
||||||
|
val (type, certificates) = json.assertHasOnlyFields("type", "certificates")
|
||||||
|
assertThat(type.textValue()).isEqualTo(certPath.type)
|
||||||
|
certificates.run {
|
||||||
|
val serialNumbers = elements().asSequence().map { it["serialNumber"].bigIntegerValue() }.toList()
|
||||||
|
assertThat(serialNumbers).isEqualTo(certPath.x509Certificates.map { it.serialNumber })
|
||||||
|
}
|
||||||
|
assertThat(mapper.convertValue<CertPath>(json).encoded).isEqualTo(certPath.encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `X509Certificate serialization`() {
|
||||||
|
val cert: X509Certificate = MINI_CORP.identity.certificate
|
||||||
|
val json = mapper.valueToTree<ObjectNode>(cert)
|
||||||
|
println(mapper.writeValueAsString(json))
|
||||||
|
assertThat(json["serialNumber"].bigIntegerValue()).isEqualTo(cert.serialNumber)
|
||||||
|
assertThat(json["issuer"].valueAs<X500Principal>(mapper)).isEqualTo(cert.issuerX500Principal)
|
||||||
|
assertThat(json["subject"].valueAs<X500Principal>(mapper)).isEqualTo(cert.subjectX500Principal)
|
||||||
|
assertThat(json["publicKey"].valueAs<PublicKey>(mapper)).isEqualTo(cert.publicKey)
|
||||||
|
assertThat(json["notAfter"].valueAs<Date>(mapper)).isEqualTo(cert.notAfter)
|
||||||
|
assertThat(json["notBefore"].valueAs<Date>(mapper)).isEqualTo(cert.notBefore)
|
||||||
|
assertThat(json["encoded"].binaryValue()).isEqualTo(cert.encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `X509Certificate deserialization`() {
|
||||||
|
val cert: X509Certificate = MINI_CORP.identity.certificate
|
||||||
|
assertThat(mapper.convertValue<X509Certificate>(mapOf("encoded" to cert.encoded))).isEqualTo(cert)
|
||||||
|
assertThat(mapper.convertValue<X509Certificate>(BinaryNode(cert.encoded))).isEqualTo(cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun X500Principal() {
|
||||||
|
testToStringSerialisation(X500Principal("CN=Common,L=London,O=Org,C=UK"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 fun JsonNode.assertHasOnlyFields(vararg fieldNames: String): List<JsonNode> {
|
||||||
|
assertThat(fieldNames()).containsOnly(*fieldNames)
|
||||||
|
return fieldNames.map { this[it] }
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
private data class TestData(val name: CordaX500Name, val summary: String, val subData: SubTestData)
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
private data class SubTestData(val value: Int)
|
||||||
|
|
||||||
|
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
|
||||||
|
override var isFullParties: Boolean = false
|
||||||
|
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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -53,6 +53,19 @@ import kotlin.reflect.KClass
|
|||||||
import kotlin.reflect.full.createInstance
|
import kotlin.reflect.full.createInstance
|
||||||
|
|
||||||
val Throwable.rootCause: Throwable get() = cause?.rootCause ?: this
|
val Throwable.rootCause: Throwable get() = cause?.rootCause ?: this
|
||||||
|
|
||||||
|
val Throwable.rootMessage: String? get() {
|
||||||
|
var message = this.message
|
||||||
|
var throwable = cause
|
||||||
|
while (throwable != null) {
|
||||||
|
if (throwable.message != null) {
|
||||||
|
message = throwable.message
|
||||||
|
}
|
||||||
|
throwable = throwable.cause
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
fun Throwable.getStackTraceAsString() = StringWriter().also { printStackTrace(PrintWriter(it)) }.toString()
|
fun Throwable.getStackTraceAsString() = StringWriter().also { printStackTrace(PrintWriter(it)) }.toString()
|
||||||
|
|
||||||
infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive)
|
infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive)
|
||||||
|
@ -184,7 +184,8 @@ object SerializationDefaults {
|
|||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing a ByteSequence, utilising the defaults.
|
* Convenience extension method for deserializing a ByteSequence, utilising the defaults.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T {
|
inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
|
context: SerializationContext = serializationFactory.defaultContext): T {
|
||||||
return serializationFactory.deserialize(this, T::class.java, context)
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,31 +194,40 @@ inline fun <reified T : Any> ByteSequence.deserialize(serializationFactory: Seri
|
|||||||
* It might be helpful to know [SerializationContext] to use the same encoding in the reply.
|
* It might be helpful to know [SerializationContext] to use the same encoding in the reply.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
inline fun <reified T : Any> ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext<T> {
|
context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext<T> {
|
||||||
return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context)
|
return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> SerializedBytes<T>.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T {
|
inline fun <reified T : Any> SerializedBytes<T>.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
|
context: SerializationContext = serializationFactory.defaultContext): T {
|
||||||
return serializationFactory.deserialize(this, T::class.java, context)
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing a ByteArray, utilising the defaults.
|
* Convenience extension method for deserializing a ByteArray, utilising the defaults.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.sequence().deserialize(serializationFactory, context)
|
inline fun <reified T : Any> ByteArray.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
|
context: SerializationContext = serializationFactory.defaultContext): T {
|
||||||
|
require(isNotEmpty()) { "Empty bytes" }
|
||||||
|
return this.sequence().deserialize(serializationFactory, context)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for deserializing a JDBC Blob, utilising the defaults.
|
* Convenience extension method for deserializing a JDBC Blob, utilising the defaults.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> Blob.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T = this.getBytes(1, this.length().toInt()).deserialize(serializationFactory, context)
|
inline fun <reified T : Any> Blob.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
|
context: SerializationContext = serializationFactory.defaultContext): T {
|
||||||
|
return this.getBytes(1, this.length().toInt()).deserialize(serializationFactory, context)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience extension method for serializing an object of type T, utilising the defaults.
|
* Convenience extension method for serializing an object of type T, utilising the defaults.
|
||||||
*/
|
*/
|
||||||
fun <T : Any> T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): SerializedBytes<T> {
|
fun <T : Any> T.serialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||||
|
context: SerializationContext = serializationFactory.defaultContext): SerializedBytes<T> {
|
||||||
return serializationFactory.serialize(this, context)
|
return serializationFactory.serialize(this, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
67
docs/source/blob-inspector.rst
Normal file
67
docs/source/blob-inspector.rst
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
Blob Inspector
|
||||||
|
==============
|
||||||
|
|
||||||
|
There are many benefits to having a custom binary serialisation format (see :doc:`serialization` for details) but one
|
||||||
|
disadvantage is the inability to view the contents in a human-friendly manner. The blob inspector tool alleviates this issue
|
||||||
|
by allowing the contents of a binary blob file (or URL end-point) to be output in either YAML or JSON. It uses
|
||||||
|
``JacksonSupport`` to do this (see :doc:`json`).
|
||||||
|
|
||||||
|
The latest version of the tool can be downloaded from `here <https://www.corda.net/downloads/>`_.
|
||||||
|
|
||||||
|
To run simply pass in the file or URL as the first parameter:
|
||||||
|
|
||||||
|
``java -jar blob-inspector.jar <file or URL>``
|
||||||
|
|
||||||
|
Use the ``--help`` flag for a full list of command line options.
|
||||||
|
|
||||||
|
When inspecting your custom data structures, there's no need to include the jars containing the class definitions for them
|
||||||
|
in the classpath. The blob inspector (or rather the serialization framework) is able to synthesis any classes found in the
|
||||||
|
blob that aren't on the classpath.
|
||||||
|
|
||||||
|
SerializedBytes
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these
|
||||||
|
out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will
|
||||||
|
see this when dealing with classes such as ``SignedData`` or other structures that attach a signature, such as the
|
||||||
|
``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory. For example, the output of a node-info
|
||||||
|
file may look like:
|
||||||
|
|
||||||
|
|
||||||
|
**-\\-format=YAML**
|
||||||
|
::
|
||||||
|
|
||||||
|
net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
|
---
|
||||||
|
raw:
|
||||||
|
class: "net.corda.core.node.NodeInfo"
|
||||||
|
deserialized:
|
||||||
|
addresses:
|
||||||
|
- "localhost:10005"
|
||||||
|
legalIdentitiesAndCerts:
|
||||||
|
- "O=BankOfCorda, L=London, C=GB"
|
||||||
|
platformVersion: 4
|
||||||
|
serial: 1527851068715
|
||||||
|
signatures:
|
||||||
|
- !!binary |-
|
||||||
|
VFRy4frbgRDbCpK1Vo88PyUoj01vbRnMR3ROR2abTFk7yJ14901aeScX/CiEP+CDGiMRsdw01cXt\nhKSobAY7Dw==
|
||||||
|
|
||||||
|
**-\\-format=JSON**
|
||||||
|
::
|
||||||
|
|
||||||
|
net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
|
{
|
||||||
|
"raw" : {
|
||||||
|
"class" : "net.corda.core.node.NodeInfo",
|
||||||
|
"deserialized" : {
|
||||||
|
"addresses" : [ "localhost:10005" ],
|
||||||
|
"legalIdentitiesAndCerts" : [ "O=BankOfCorda, L=London, C=GB" ],
|
||||||
|
"platformVersion" : 4,
|
||||||
|
"serial" : 1527851068715
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signatures" : [ "VFRy4frbgRDbCpK1Vo88PyUoj01vbRnMR3ROR2abTFk7yJ14901aeScX/CiEP+CDGiMRsdw01cXthKSobAY7Dw==" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
Notice the file is actually a serialised ``SignedNodeInfo`` object, which has a ``raw`` property of type ``SerializedBytes<NodeInfo>``.
|
||||||
|
This property is materialised into a ``NodeInfo`` and is output under the ``deserialized`` field.
|
@ -1,11 +1,37 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
Here's a summary of what's changed in each Corda release. For guidance on how to upgrade code from the previous
|
||||||
|
release, see :doc:`upgrade-notes`.
|
||||||
|
|
||||||
.. _changelog_v3.2:
|
.. _changelog_v3.2:
|
||||||
|
|
||||||
Version 3.2
|
Version 3.2
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
* New tool: the BlobInspector command line tool for decoding Network Parameters and Network Map serialised bnary blobs
|
||||||
|
|
||||||
|
* 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 the name
|
||||||
|
* ``SerializedBytes`` is serialised by materialising the bytes into the object it represents, and then serialising that
|
||||||
|
object into YAML/JSON
|
||||||
|
* ``X509Certificate`` is serialised as an object with key fields such as ``issuer``, ``publicKey``, ``serialNumber``, etc.
|
||||||
|
The encoded bytes are also serialised into the ``encoded`` field. This can be used to deserialise an ``X509Certificate``
|
||||||
|
back.
|
||||||
|
* ``CertPath`` objects are serialised as a list of ``X509Certificate`` objects.
|
||||||
|
|
||||||
|
* ``fullParties`` boolean parameter added to ``JacksonSupport.createDefaultMapper`` and ``createNonRpcMapper``. If ``true``
|
||||||
|
then ``Party`` objects are serialised as JSON objects with the ``name`` and ``owningKey`` fields. For ``PartyAndCertificate``
|
||||||
|
the ``certPath`` is serialised.
|
||||||
|
|
||||||
|
* Several members of ``JacksonSupport`` have been deprecated to highlight that they are internal and not to be used
|
||||||
|
|
||||||
* Doorman and NetworkMap URLs can now be configured individually rather than being assumed to be
|
* Doorman and NetworkMap URLs can now be configured individually rather than being assumed to be
|
||||||
the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file`
|
the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file`
|
||||||
and :doc:`permissioning` for details.
|
and :doc:`permissioning` for details.
|
||||||
|
@ -148,8 +148,8 @@ Where ``newCampaign`` is a parameter of type ``Campaign``.
|
|||||||
|
|
||||||
Mappings from strings to types
|
Mappings from strings to types
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Several parameter types can automatically be mapped from strings. See the `defined parsers`_ for more information. We
|
In addition to the types already supported by Jackson, several parameter types can automatically be mapped from strings.
|
||||||
cover the most common types here.
|
We cover the most common types here.
|
||||||
|
|
||||||
Amount
|
Amount
|
||||||
~~~~~~
|
~~~~~~
|
||||||
@ -158,23 +158,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
|
* 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")
|
* 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
|
OpaqueBytes
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
A parameter of type ``OpaqueBytes`` can be provided as a string, which will be automatically converted to
|
A parameter of type ``OpaqueBytes`` can be provided as a UTF-8 string.
|
||||||
``OpaqueBytes``.
|
|
||||||
|
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
|
Party
|
||||||
~~~~~
|
~~~~~
|
||||||
A parameter of type ``Party`` can be written in several ways:
|
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 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
|
* By specifying any other non-ambiguous part of the name: ``"Sao Paulo"`` (if only one network node is located in Sao
|
||||||
Paulo)
|
Paulo)
|
||||||
|
* By specifying the public key (see above)
|
||||||
|
|
||||||
Instant
|
NodeInfo
|
||||||
~~~~~~~
|
~~~~~~~~
|
||||||
A parameter of type ``Instant`` can be written as follows: ``"2017-12-22T00:00:00Z"``.
|
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
|
Examples
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
@ -258,6 +279,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
|
* 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
|
.. _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/
|
.. _Groovy: http://groovy-lang.org/
|
||||||
.. _CRaSH: http://www.crashub.org/
|
.. _CRaSH: http://www.crashub.org/
|
||||||
|
@ -4,6 +4,7 @@ Tools
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
blob-inspector
|
||||||
network-simulator
|
network-simulator
|
||||||
demobench
|
demobench
|
||||||
node-explorer
|
node-explorer
|
||||||
|
@ -6,11 +6,11 @@ import com.esotericsoftware.kryo.io.Output
|
|||||||
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
||||||
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
||||||
import com.esotericsoftware.kryo.util.Util
|
import com.esotericsoftware.kryo.util.Util
|
||||||
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
|
||||||
import net.corda.core.serialization.ClassWhitelist
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy
|
import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer
|
import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
|
@ -26,7 +26,7 @@ data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope)
|
|||||||
class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
||||||
private val objectHistory: MutableList<Any> = mutableListOf()
|
private val objectHistory: MutableList<Any> = mutableListOf()
|
||||||
|
|
||||||
internal companion object {
|
companion object {
|
||||||
private val BYTES_NEEDED_TO_PEEK: Int = 23
|
private val BYTES_NEEDED_TO_PEEK: Int = 23
|
||||||
|
|
||||||
fun peekSize(bytes: ByteArray): Int {
|
fun peekSize(bytes: ByteArray): Int {
|
||||||
@ -48,6 +48,23 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
}
|
}
|
||||||
return size + BYTES_NEEDED_TO_PEEK
|
return size + BYTES_NEEDED_TO_PEEK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(NotSerializableException::class)
|
||||||
|
fun getEnvelope(bytes: ByteSequence): Envelope {
|
||||||
|
// Check that the lead bytes match expected header
|
||||||
|
val headerSize = AmqpHeaderV1_0.size
|
||||||
|
if (bytes.take(headerSize) != AmqpHeaderV1_0) {
|
||||||
|
throw NotSerializableException("Serialization header does not match.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = Data.Factory.create()
|
||||||
|
val size = data.decode(ByteBuffer.wrap(bytes.bytes, bytes.offset + headerSize, bytes.size - headerSize))
|
||||||
|
if (size.toInt() != bytes.size - headerSize) {
|
||||||
|
throw NotSerializableException("Unexpected size of data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Envelope.get(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
@ -57,23 +74,6 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
|
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
|
||||||
deserializeAndReturnEnvelope(bytes, T::class.java)
|
deserializeAndReturnEnvelope(bytes, T::class.java)
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
|
||||||
internal fun getEnvelope(bytes: ByteSequence): Envelope {
|
|
||||||
// Check that the lead bytes match expected header
|
|
||||||
val headerSize = AmqpHeaderV1_0.size
|
|
||||||
if (bytes.take(headerSize) != AmqpHeaderV1_0) {
|
|
||||||
throw NotSerializableException("Serialization header does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val data = Data.Factory.create()
|
|
||||||
val size = data.decode(ByteBuffer.wrap(bytes.bytes, bytes.offset + headerSize, bytes.size - headerSize))
|
|
||||||
if (size.toInt() != bytes.size - headerSize) {
|
|
||||||
throw NotSerializableException("Unexpected size of data")
|
|
||||||
}
|
|
||||||
|
|
||||||
return Envelope.get(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
private fun <R> des(generator: () -> R): R {
|
private fun <R> des(generator: () -> R): R {
|
||||||
try {
|
try {
|
||||||
|
@ -2,15 +2,17 @@ package net.corda.nodeapi.internal.serialization
|
|||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo
|
import com.esotericsoftware.kryo.Kryo
|
||||||
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.node.services.statemachine.DataSessionMessage
|
import net.corda.node.services.statemachine.DataSessionMessage
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.Envelope
|
import net.corda.nodeapi.internal.serialization.amqp.Envelope
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||||
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.internal.amqpSpecific
|
import net.corda.testing.internal.amqpSpecific
|
||||||
import net.corda.testing.internal.kryoSpecific
|
import net.corda.testing.internal.kryoSpecific
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
|
||||||
import org.assertj.core.api.Assertions
|
import org.assertj.core.api.Assertions
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@ -27,8 +29,7 @@ class ListsSerializationTest {
|
|||||||
|
|
||||||
fun <T : Any> verifyEnvelope(serBytes: SerializedBytes<T>, envVerBody: (Envelope) -> Unit) =
|
fun <T : Any> verifyEnvelope(serBytes: SerializedBytes<T>, envVerBody: (Envelope) -> Unit) =
|
||||||
amqpSpecific("AMQP specific envelope verification") {
|
amqpSpecific("AMQP specific envelope verification") {
|
||||||
val context = SerializationFactory.defaultFactory.defaultContext
|
val envelope = DeserializationInput.getEnvelope(serBytes)
|
||||||
val envelope = DeserializationInput(SerializerFactory(context.whitelist, context.deserializationClassLoader)).getEnvelope(serBytes)
|
|
||||||
envVerBody(envelope)
|
envVerBody(envelope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ dependencies {
|
|||||||
compile "org.fusesource.jansi:jansi:$jansi_version"
|
compile "org.fusesource.jansi:jansi:$jansi_version"
|
||||||
|
|
||||||
// Manifests: for reading stuff from the manifest file
|
// Manifests: for reading stuff from the manifest file
|
||||||
compile "com.jcabi:jcabi-manifests:1.1"
|
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||||
|
|
||||||
compile("com.intellij:forms_rt:7.0.3") {
|
compile("com.intellij:forms_rt:7.0.3") {
|
||||||
exclude group: "asm"
|
exclude group: "asm"
|
||||||
@ -96,7 +96,6 @@ dependencies {
|
|||||||
|
|
||||||
// Jackson support: serialisation to/from JSON, YAML, etc
|
// Jackson support: serialisation to/from JSON, YAML, etc
|
||||||
compile project(':client:jackson')
|
compile project(':client:jackson')
|
||||||
compile group: 'org.json', name: 'json', version: json_version
|
|
||||||
|
|
||||||
// Coda Hale's Metrics: for monitoring of key statistics
|
// Coda Hale's Metrics: for monitoring of key statistics
|
||||||
compile "io.dropwizard.metrics:metrics-core:3.1.2"
|
compile "io.dropwizard.metrics:metrics-core:3.1.2"
|
||||||
|
@ -14,7 +14,6 @@ import net.corda.core.concurrent.CordaFuture
|
|||||||
import net.corda.core.contracts.UniqueIdentifier
|
import net.corda.core.contracts.UniqueIdentifier
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.concurrent.doneFuture
|
import net.corda.core.internal.concurrent.doneFuture
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
@ -22,9 +21,7 @@ import net.corda.core.messaging.CordaRPCOps
|
|||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.messaging.FlowProgressHandle
|
import net.corda.core.messaging.FlowProgressHandle
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.node.NodeInfo
|
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.node.internal.security.AdminSubject
|
import net.corda.node.internal.security.AdminSubject
|
||||||
@ -54,7 +51,6 @@ import org.crsh.util.Utils
|
|||||||
import org.crsh.vfs.FS
|
import org.crsh.vfs.FS
|
||||||
import org.crsh.vfs.spi.file.FileMountFactory
|
import org.crsh.vfs.spi.file.FileMountFactory
|
||||||
import org.crsh.vfs.spi.url.ClassPathMountFactory
|
import org.crsh.vfs.spi.url.ClassPathMountFactory
|
||||||
import org.json.JSONObject
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscriber
|
import rx.Subscriber
|
||||||
@ -191,43 +187,22 @@ object InteractiveShell {
|
|||||||
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
|
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
|
||||||
// serializers.
|
// serializers.
|
||||||
JacksonSupport.createInMemoryMapper(identityService, YAMLFactory(), true).apply {
|
JacksonSupport.createInMemoryMapper(identityService, YAMLFactory(), true).apply {
|
||||||
val rpcModule = SimpleModule()
|
val rpcModule = SimpleModule().apply {
|
||||||
rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer)
|
addDeserializer(InputStream::class.java, InputStreamDeserializer)
|
||||||
rpcModule.addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer)
|
addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer)
|
||||||
rpcModule.addDeserializer(UUID::class.java, UUIDDeserializer)
|
}
|
||||||
registerModule(rpcModule)
|
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 {
|
private fun createOutputMapper(): ObjectMapper {
|
||||||
|
|
||||||
return JacksonSupport.createNonRpcMapper().apply {
|
return JacksonSupport.createNonRpcMapper().apply {
|
||||||
// Register serializers for stateful objects from libraries that are special to the RPC system and don't
|
// 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.
|
// make sense to print out to the screen. For classes we own, annotations can be used instead.
|
||||||
val rpcModule = SimpleModule()
|
val rpcModule = SimpleModule().apply {
|
||||||
rpcModule.addSerializer(Observable::class.java, ObservableSerializer)
|
addSerializer(Observable::class.java, ObservableSerializer)
|
||||||
rpcModule.addSerializer(InputStream::class.java, InputStreamSerializer)
|
addSerializer(InputStream::class.java, InputStreamSerializer)
|
||||||
rpcModule.addSerializer(NodeInfo::class.java, NodeInfoSerializer)
|
}
|
||||||
registerModule(rpcModule)
|
registerModule(rpcModule)
|
||||||
|
|
||||||
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
||||||
@ -245,7 +220,9 @@ object InteractiveShell {
|
|||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer) {
|
fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer) {
|
||||||
val matches = rpcOps.registeredFlows().filter { nameFragment in it }
|
val matches =
|
||||||
|
rpcOps.registeredFlows().filter { nameFragment in it }
|
||||||
|
|
||||||
if (matches.isEmpty()) {
|
if (matches.isEmpty()) {
|
||||||
output.println("No matching flow found, run 'flow list' to see your options.", Color.red)
|
output.println("No matching flow found, run 'flow list' to see your options.", Color.red)
|
||||||
return
|
return
|
||||||
@ -573,15 +550,5 @@ object InteractiveShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ class CustomTypeJsonParsingTests {
|
|||||||
objectMapper = ObjectMapper()
|
objectMapper = ObjectMapper()
|
||||||
val simpleModule = SimpleModule()
|
val simpleModule = SimpleModule()
|
||||||
simpleModule.addDeserializer(UniqueIdentifier::class.java, InteractiveShell.UniqueIdentifierDeserializer)
|
simpleModule.addDeserializer(UniqueIdentifier::class.java, InteractiveShell.UniqueIdentifierDeserializer)
|
||||||
simpleModule.addDeserializer(UUID::class.java, InteractiveShell.UUIDDeserializer)
|
|
||||||
objectMapper.registerModule(simpleModule)
|
objectMapper.registerModule(simpleModule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"fixedLeg": {
|
"fixedLeg": {
|
||||||
"fixedRatePayer": "MCowBQYDK2VwAyEAzswVB9wd3XKVlRwpCIjwla25BE0bc9aW5t8GXWg71Pw=",
|
"fixedRatePayer": "GfHq2tTVk9z4eXgyUEefbHpUFfpnDvsFoZVZe3ikrLbwdRA4jebSJPykJwgw",
|
||||||
"notional": "$25000000",
|
"notional": "$25000000",
|
||||||
"paymentFrequency": "SemiAnnual",
|
"paymentFrequency": "SemiAnnual",
|
||||||
"effectiveDate": "2016-03-11",
|
"effectiveDate": "2016-03-11",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"interestPeriodAdjustment": "Adjusted"
|
"interestPeriodAdjustment": "Adjusted"
|
||||||
},
|
},
|
||||||
"floatingLeg": {
|
"floatingLeg": {
|
||||||
"floatingRatePayer": "MCowBQYDK2VwAyEAa3nFfmoJUjkoLASBjpYRLz8DpAAbqXpWTCOFKj8epfw=",
|
"floatingRatePayer": "GfHq2tTVk9z4eXgyMYwWRYKSgGpARSquPTt8V4Z54RmNe2SJ7BUq2jSUUfvT",
|
||||||
"notional": {
|
"notional": {
|
||||||
"quantity": 2500000000,
|
"quantity": 2500000000,
|
||||||
"token": "USD"
|
"token": "USD"
|
||||||
|
@ -35,6 +35,7 @@ include 'tools:loadtest'
|
|||||||
include 'tools:graphs'
|
include 'tools:graphs'
|
||||||
include 'tools:bootstrapper'
|
include 'tools:bootstrapper'
|
||||||
include 'tools:network-bootstrapper'
|
include 'tools:network-bootstrapper'
|
||||||
|
include 'tools:blobinspector'
|
||||||
include 'example-code'
|
include 'example-code'
|
||||||
project(':example-code').projectDir = file("$settingsDir/docs/source/example-code")
|
project(':example-code').projectDir = file("$settingsDir/docs/source/example-code")
|
||||||
include 'samples:attachment-demo'
|
include 'samples:attachment-demo'
|
||||||
|
@ -41,7 +41,7 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKe
|
|||||||
|
|
||||||
fun build(serial: Long = 1, platformVersion: Int = 1): NodeInfo {
|
fun build(serial: Long = 1, platformVersion: Int = 1): NodeInfo {
|
||||||
return 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 },
|
identitiesAndPrivateKeys.map { it.first },
|
||||||
platformVersion,
|
platformVersion,
|
||||||
serial
|
serial
|
||||||
|
29
tools/blobinspector/build.gradle
Normal file
29
tools/blobinspector/build.gradle
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':client:jackson')
|
||||||
|
compile project(':node-api')
|
||||||
|
compile 'info.picocli:picocli:3.0.0'
|
||||||
|
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
||||||
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
|
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||||
|
|
||||||
|
testCompile project(':test-utils')
|
||||||
|
testCompile "junit:junit:$junit_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
|
||||||
|
exclude "META-INF/*.SF"
|
||||||
|
exclude "META-INF/*.DSA"
|
||||||
|
exclude "META-INF/*.RSA"
|
||||||
|
}
|
||||||
|
baseName 'blobinspector'
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
'Automatic-Module-Name': 'net.corda.blobinspector',
|
||||||
|
'Main-Class': 'net.corda.blobinspector.MainKt'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package net.corda.blobinspector
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||||
|
import com.jcabi.manifests.Manifests
|
||||||
|
import net.corda.client.jackson.JacksonSupport
|
||||||
|
import net.corda.core.internal.isRegularFile
|
||||||
|
import net.corda.core.internal.rootMessage
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
|
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||||
|
import net.corda.core.utilities.ByteSequence
|
||||||
|
import net.corda.core.utilities.sequence
|
||||||
|
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||||
|
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||||
|
import picocli.CommandLine
|
||||||
|
import picocli.CommandLine.*
|
||||||
|
import java.net.MalformedURLException
|
||||||
|
import java.net.URL
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val main = Main()
|
||||||
|
try {
|
||||||
|
CommandLine.run(main, *args)
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
val throwable = e.cause ?: e
|
||||||
|
if (main.verbose) {
|
||||||
|
throwable.printStackTrace()
|
||||||
|
} else {
|
||||||
|
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
|
||||||
|
}
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "Blob Inspector",
|
||||||
|
versionProvider = CordaVersionProvider::class,
|
||||||
|
mixinStandardHelpOptions = true, // add --help and --version options,
|
||||||
|
showDefaultValues = true,
|
||||||
|
description = arrayOf("Inspect AMQP serialised binary blobs")
|
||||||
|
)
|
||||||
|
class Main : Runnable {
|
||||||
|
@Parameters(index = "0", paramLabel = "SOURCE", description = arrayOf("URL or file path to the blob"), converter = arrayOf(SourceConverter::class))
|
||||||
|
private var source: URL? = null
|
||||||
|
|
||||||
|
@Option(names = arrayOf("--format"), paramLabel = "type", description = arrayOf("Output format. Possible values: [YAML, JSON]"))
|
||||||
|
private var formatType: FormatType = FormatType.YAML
|
||||||
|
|
||||||
|
@Option(names = arrayOf("--full-parties"),
|
||||||
|
description = arrayOf("Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"))
|
||||||
|
private var fullParties: Boolean = false
|
||||||
|
|
||||||
|
@Option(names = arrayOf("--schema"), description = arrayOf("Print the blob's schema first"))
|
||||||
|
private var schema: Boolean = false
|
||||||
|
|
||||||
|
@Option(names = arrayOf("--verbose"), description = arrayOf("Enable verbose output"))
|
||||||
|
var verbose: Boolean = false
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
if (verbose) {
|
||||||
|
System.setProperty("logLevel", "trace")
|
||||||
|
}
|
||||||
|
|
||||||
|
val bytes = source!!.readBytes().run {
|
||||||
|
require(size > AmqpHeaderV1_0.size) { "Insufficient bytes for AMQP blob" }
|
||||||
|
sequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
require(bytes.take(AmqpHeaderV1_0.size) == AmqpHeaderV1_0) { "Not an AMQP blob" }
|
||||||
|
|
||||||
|
if (schema) {
|
||||||
|
val envelope = DeserializationInput.getEnvelope(bytes)
|
||||||
|
println(envelope.schema)
|
||||||
|
println()
|
||||||
|
println(envelope.transformsSchema)
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseSerialization()
|
||||||
|
|
||||||
|
val factory = when (formatType) {
|
||||||
|
FormatType.YAML -> YAMLFactory()
|
||||||
|
FormatType.JSON -> JsonFactory()
|
||||||
|
}
|
||||||
|
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
||||||
|
|
||||||
|
val deserialized = bytes.deserialize<Any>()
|
||||||
|
println(deserialized.javaClass.name)
|
||||||
|
mapper.writeValue(System.out, deserialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initialiseSerialization() {
|
||||||
|
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||||
|
SerializationFactoryImpl().apply {
|
||||||
|
registerScheme(AMQPInspectorSerializationScheme)
|
||||||
|
},
|
||||||
|
AMQP_P2P_CONTEXT
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||||
|
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||||
|
return byteSequence == AmqpHeaderV1_0 && target == SerializationContext.UseCase.P2P
|
||||||
|
}
|
||||||
|
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
|
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SourceConverter : ITypeConverter<URL> {
|
||||||
|
override fun convert(value: String): URL {
|
||||||
|
return try {
|
||||||
|
URL(value)
|
||||||
|
} catch (e: MalformedURLException) {
|
||||||
|
val path = Paths.get(value)
|
||||||
|
require(path.isRegularFile()) { "$path is not a file" }
|
||||||
|
path.toUri().toURL()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CordaVersionProvider : IVersionProvider {
|
||||||
|
override fun getVersion(): Array<String> {
|
||||||
|
return arrayOf(
|
||||||
|
"Version: ${Manifests.read("Corda-Release-Version")}",
|
||||||
|
"Revision: ${Manifests.read("Corda-Revision")}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class FormatType { YAML, JSON }
|
||||||
|
|
16
tools/blobinspector/src/main/resources/log4j2.xml
Normal file
16
tools/blobinspector/src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="info">
|
||||||
|
<Properties>
|
||||||
|
<Property name="logLevel">off</Property>
|
||||||
|
</Properties>
|
||||||
|
<Appenders>
|
||||||
|
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
|
||||||
|
<PatternLayout pattern="[%C{1}.%M] %m%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="${sys:logLevel}">
|
||||||
|
<AppenderRef ref="STDOUT"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
Loading…
x
Reference in New Issue
Block a user