diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 9632eef3de..5cd3aa02f4 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -71,6 +71,8 @@
+
+
diff --git a/build.gradle b/build.gradle
index ea49fc62ba..36c4bfc2ad 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,7 +37,7 @@ buildscript {
* https://issues.apache.org/jira/browse/ARTEMIS-1559
*/
ext.artemis_version = '2.5.0'
- ext.jackson_version = '2.9.3'
+ ext.jackson_version = '2.9.5'
ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.assertj_version = '3.8.0'
diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
index af64d56aa4..2820a7d15f 100644
--- a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
@@ -22,9 +23,7 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.identity.*
-import net.corda.core.internal.CertRole
-import net.corda.core.internal.VisibleForTesting
-import net.corda.core.internal.uncheckedCast
+import net.corda.core.internal.*
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
@@ -37,7 +36,6 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.core.utilities.toBase58String
import org.bouncycastle.asn1.x509.KeyPurposeId
-import java.lang.reflect.Modifier
import java.math.BigDecimal
import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey
@@ -169,7 +167,9 @@ object JacksonSupport {
addSerializer(Date::class.java, DateSerializer)
})
registerModule(CordaModule())
- registerModule(KotlinModule())
+ registerModule(KotlinModule().apply {
+ setDeserializerModifier(KotlinObjectDeserializerModifier)
+ })
addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java)
addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java)
@@ -178,6 +178,19 @@ object JacksonSupport {
}
}
+ private object KotlinObjectDeserializerModifier : BeanDeserializerModifier() {
+ override fun modifyDeserializer(config: DeserializationConfig,
+ beanDesc: BeanDescription,
+ deserializer: JsonDeserializer<*>): JsonDeserializer<*> {
+ val objectInstance = beanDesc.beanClass.kotlinObjectInstance
+ return if (objectInstance != null) KotlinObjectDeserializer(objectInstance) else deserializer
+ }
+ }
+
+ private class KotlinObjectDeserializer(private val objectInstance: T) : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): T = objectInstance
+ }
+
@ToStringSerialize
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
private interface BigDecimalMixin
@@ -210,7 +223,7 @@ object JacksonSupport {
val keyPurposeIds = KeyPurposeId::class.java
.fields
- .filter { Modifier.isStatic(it.modifiers) && it.type == KeyPurposeId::class.java }
+ .filter { it.isStatic && it.type == KeyPurposeId::class.java }
.associateBy({ (it.get(null) as KeyPurposeId).id }, { it.name })
val knownExtensions = setOf(
@@ -235,7 +248,7 @@ object JacksonSupport {
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) })
+ writeObjectField("extendedKeyUsage", value.extendedKeyUsage.map { keyPurposeIds[it] ?: it })
jsonObject("basicConstraints") {
val isCa = value.basicConstraints != -1
writeBooleanField("isCA", isCa)
diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt
index 5dd3fe395a..32ca33befe 100644
--- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt
@@ -2,30 +2,39 @@
package net.corda.client.jackson.internal
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonInclude.Include
+import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.*
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.IntNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
+import com.google.common.primitives.Booleans
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.contracts.*
+import net.corda.core.crypto.*
+import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
+import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.node.NodeInfo
-import net.corda.core.serialization.*
-import net.corda.core.transactions.SignedTransaction
-import net.corda.core.transactions.WireTransaction
+import net.corda.core.serialization.SerializedBytes
+import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.serialize
+import net.corda.core.transactions.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.parseAsHex
+import net.corda.core.utilities.toHexString
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.constructorForDeserialization
@@ -33,6 +42,7 @@ import net.corda.serialization.internal.amqp.hasCordaSerializable
import net.corda.serialization.internal.amqp.propertiesForSerialization
import java.security.PublicKey
import java.security.cert.CertPath
+import java.time.Instant
class CordaModule : SimpleModule("corda-core") {
override fun setupModule(context: SetupContext) {
@@ -50,12 +60,20 @@ class CordaModule : SimpleModule("corda-core") {
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(SecureHash::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, SignedTransactionMixin::class.java)
- context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java)
+ context.setMixInAnnotations(WireTransaction::class.java, WireTransactionMixin::class.java)
+ context.setMixInAnnotations(TransactionState::class.java, TransactionStateMixin::class.java)
+ context.setMixInAnnotations(Command::class.java, CommandMixin::class.java)
+ context.setMixInAnnotations(TimeWindow::class.java, TimeWindowMixin::class.java)
+ context.setMixInAnnotations(PrivacySalt::class.java, PrivacySaltMixin::class.java)
+ context.setMixInAnnotations(SignatureScheme::class.java, SignatureSchemeMixin::class.java)
+ context.setMixInAnnotations(SignatureMetadata::class.java, SignatureMetadataMixin::class.java)
+ context.setMixInAnnotations(PartialTree::class.java, PartialTreeMixin::class.java)
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
}
}
@@ -64,21 +82,23 @@ class CordaModule : SimpleModule("corda-core") {
* Use the same properties that AMQP serialization uses if the POJO is @CordaSerializable
*/
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
- // We need a SerializerFactory when scanning for properties but don't actually use it so any will do
- private val serializerFactory = SerializerFactory(AllWhitelist, Thread.currentThread().contextClassLoader)
+ // We need to pass in a SerializerFactory when scanning for properties, but don't actually do any serialisation so any will do.
+ private val serializerFactory = SerializerFactory(AllWhitelist, javaClass.classLoader)
override fun changeProperties(config: SerializationConfig,
beanDesc: BeanDescription,
beanProperties: MutableList): MutableList {
- if (hasCordaSerializable(beanDesc.beanClass)) {
- val ctor = constructorForDeserialization(beanDesc.beanClass)
- val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory)
+ val beanClass = beanDesc.beanClass
+ if (hasCordaSerializable(beanClass) && beanClass.kotlinObjectInstance == null) {
+ val ctor = constructorForDeserialization(beanClass)
+ val amqpProperties = propertiesForSerialization(ctor, beanClass, serializerFactory)
.serializationOrder
.map { it.serializer.name }
- beanProperties.removeIf { it.name !in amqpProperties }
- (amqpProperties - beanProperties.map { it.name }).let {
+ val propertyRenames = beanDesc.findProperties().associateBy({ it.name }, { it.internalName })
+ (amqpProperties - propertyRenames.values).let {
check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" }
}
+ beanProperties.removeIf { propertyRenames[it.name] !in amqpProperties }
}
return beanProperties
}
@@ -88,11 +108,7 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier()
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
private interface NetworkHostAndPortMixin
-private class NetworkHostAndPortDeserializer : JsonDeserializer() {
- override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
- return NetworkHostAndPort.parse(parser.text)
- }
-}
+private class NetworkHostAndPortDeserializer : SimpleDeserializer({ NetworkHostAndPort.parse(text) })
@JsonSerialize(using = PartyAndCertificateSerializer::class)
// TODO Add deserialization which follows the same lookup logic as Party
@@ -102,14 +118,14 @@ private class PartyAndCertificateSerializer : JsonSerializer() {
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
- gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
+ val core = value.coreTransaction
+ val stxJson = when (core) {
+ is WireTransaction -> StxJson(wire = core, signatures = value.sigs)
+ is FilteredTransaction -> StxJson(filtered = core, signatures = value.sigs)
+ is NotaryChangeWireTransaction -> StxJson(notaryChangeWire = core, signatures = value.sigs)
+ is ContractUpgradeWireTransaction -> StxJson(contractUpgradeWire = core, signatures = value.sigs)
+ is ContractUpgradeFilteredTransaction -> StxJson(contractUpgradeFiltered = core, signatures = value.sigs)
+ else -> throw IllegalArgumentException("Don't know about ${core.javaClass}")
+ }
+ gen.writeObject(stxJson)
}
}
private class SignedTransactionDeserializer : JsonDeserializer() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
- val wrapper = parser.readValueAs()
- return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
+ val wrapper = parser.readValueAs()
+ val core = wrapper.run { wire ?: filtered ?: notaryChangeWire ?: contractUpgradeWire ?: contractUpgradeFiltered!! }
+ return SignedTransaction(core, wrapper.signatures)
}
}
-private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List)
+@JsonInclude(Include.NON_NULL)
+private data class StxJson(
+ val wire: WireTransaction? = null,
+ val filtered: FilteredTransaction? = null,
+ val notaryChangeWire: NotaryChangeWireTransaction? = null,
+ val contractUpgradeWire: ContractUpgradeWireTransaction? = null,
+ val contractUpgradeFiltered: ContractUpgradeFilteredTransaction? = null,
+ val signatures: List
+) {
+ init {
+ val count = Booleans.countTrue(wire != null, filtered != null, notaryChangeWire != null, contractUpgradeWire != null, contractUpgradeFiltered != null)
+ require(count == 1) { this }
+ }
+}
+
+@JsonSerialize(using = WireTransactionSerializer::class)
+@JsonDeserialize(using = WireTransactionDeserializer::class)
+private interface WireTransactionMixin
+
+private class WireTransactionSerializer : JsonSerializer() {
+ override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.writeObject(WireTransactionJson(
+ value.id,
+ value.notary,
+ value.inputs,
+ value.outputs,
+ value.commands,
+ value.timeWindow,
+ value.attachments,
+ value.privacySalt
+ ))
+ }
+}
+
+private class WireTransactionDeserializer : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): WireTransaction {
+ val wrapper = parser.readValueAs()
+ val componentGroups = WireTransaction.createComponentGroups(
+ wrapper.inputs,
+ wrapper.outputs,
+ wrapper.commands,
+ wrapper.attachments,
+ wrapper.notary,
+ wrapper.timeWindow
+ )
+ return WireTransaction(componentGroups, wrapper.privacySalt)
+ }
+}
+
+private class WireTransactionJson(val id: SecureHash,
+ val notary: Party?,
+ val inputs: List,
+ val outputs: List>,
+ val commands: List>,
+ val timeWindow: TimeWindow?,
+ val attachments: List,
+ val privacySalt: PrivacySalt)
+
+private interface TransactionStateMixin {
+ @get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ val data: ContractState
+ @get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ val constraint: AttachmentConstraint
+}
+
+private interface CommandMixin {
+ @get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ val value: CommandData
+}
+
+@JsonDeserialize(using = TimeWindowDeserializer::class)
+private interface TimeWindowMixin
+
+private class TimeWindowDeserializer : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): TimeWindow {
+ return parser.readValueAs().run {
+ when {
+ fromTime != null && untilTime != null -> TimeWindow.between(fromTime, untilTime)
+ fromTime != null -> TimeWindow.fromOnly(fromTime)
+ untilTime != null -> TimeWindow.untilOnly(untilTime)
+ else -> throw JsonParseException(parser, "Neither fromTime nor untilTime exists for TimeWindow")
+ }
+ }
+ }
+}
+
+private data class TimeWindowJson(val fromTime: Instant?, val untilTime: Instant?)
+
+@JsonSerialize(using = PrivacySaltSerializer::class)
+@JsonDeserialize(using = PrivacySaltDeserializer::class)
+private interface PrivacySaltMixin
+
+private class PrivacySaltSerializer : JsonSerializer() {
+ override fun serialize(value: PrivacySalt, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.writeString(value.bytes.toHexString())
+ }
+}
+
+private class PrivacySaltDeserializer : SimpleDeserializer({ PrivacySalt(text.parseAsHex()) })
+
+// TODO Add a lookup function by number ID in Crypto
+private val signatureSchemesByNumberID = Crypto.supportedSignatureSchemes().associateBy { it.schemeNumberID }
+
+@JsonSerialize(using = SignatureMetadataSerializer::class)
+@JsonDeserialize(using = SignatureMetadataDeserializer::class)
+private interface SignatureMetadataMixin
+
+private class SignatureMetadataSerializer : JsonSerializer() {
+ override fun serialize(value: SignatureMetadata, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.jsonObject {
+ writeNumberField("platformVersion", value.platformVersion)
+ writeObjectField("scheme", value.schemeNumberID.let { signatureSchemesByNumberID[it] ?: it })
+ }
+ }
+}
+
+private class SignatureMetadataDeserializer : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureMetadata {
+ val json = parser.readValueAsTree()
+ val scheme = json["scheme"]
+ val schemeNumberID = if (scheme is IntNode) {
+ scheme.intValue()
+ } else {
+ Crypto.findSignatureScheme(scheme.textValue()).schemeNumberID
+ }
+ return SignatureMetadata(json["platformVersion"].intValue(), schemeNumberID)
+ }
+}
+
+@JsonSerialize(using = PartialTreeSerializer::class)
+@JsonDeserialize(using = PartialTreeDeserializer::class)
+private interface PartialTreeMixin
+
+private class PartialTreeSerializer : JsonSerializer() {
+ override fun serialize(value: PartialTree, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.writeObject(convert(value))
+ }
+
+ private fun convert(tree: PartialTree): PartialTreeJson {
+ return when (tree) {
+ is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash)
+ is PartialTree.Leaf -> PartialTreeJson(leaf = tree.hash)
+ is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right))
+ else -> throw IllegalArgumentException("Don't know how to serialize $tree")
+ }
+ }
+}
+
+private class PartialTreeDeserializer : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): PartialTree {
+ return convert(parser.readValueAs(PartialTreeJson::class.java))
+ }
+
+ private fun convert(wrapper: PartialTreeJson): PartialTree {
+ return wrapper.run {
+ when {
+ includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf)
+ leaf != null -> PartialTree.Leaf(leaf)
+ else -> PartialTree.Node(convert(left!!), convert(right!!))
+ }
+ }
+ }
+}
+
+@JsonInclude(Include.NON_NULL)
+private class PartialTreeJson(val includedLeaf: SecureHash? = null,
+ val leaf: SecureHash? = null,
+ val left: PartialTreeJson? = null,
+ val right: PartialTreeJson? = null) {
+ init {
+ if (includedLeaf != null) {
+ require(leaf == null && left == null && right == null)
+ } else if (leaf != null) {
+ require(left == null && right == null)
+ } else {
+ require(left != null && right != null)
+ }
+ }
+}
+
+@JsonSerialize(using = SignatureSchemeSerializer::class)
+@JsonDeserialize(using = SignatureSchemeDeserializer::class)
+private interface SignatureSchemeMixin
+
+private class SignatureSchemeSerializer : JsonSerializer() {
+ override fun serialize(value: SignatureScheme, gen: JsonGenerator, serializers: SerializerProvider) {
+ gen.writeString(value.schemeCodeName)
+ }
+}
+
+private class SignatureSchemeDeserializer : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignatureScheme {
+ return if (parser.currentToken == JsonToken.VALUE_NUMBER_INT) {
+ signatureSchemesByNumberID[parser.intValue] ?: throw JsonParseException(parser, "Unable to find SignatureScheme ${parser.text}")
+ } else {
+ Crypto.findSignatureScheme(parser.text)
+ }
+ }
+}
@JsonSerialize(using = SerializedBytesSerializer::class)
@JsonDeserialize(using = SerializedBytesDeserializer::class)
diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt
index 948b18f956..255aa85855 100644
--- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt
+++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/JacksonUtils.kt
@@ -3,6 +3,8 @@ 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.DeserializationContext
+import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.annotation.JsonSerialize
@@ -20,6 +22,14 @@ inline fun JsonParser.readValueAs(): T = readValueAs(T::class.java)
inline fun JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
+inline fun JsonNode.childrenAs(mapper: ObjectMapper): List {
+ return elements().asSequence().map { it.valueAs(mapper) }.toList()
+}
+
@JacksonAnnotationsInside
@JsonSerialize(using = ToStringSerializer::class)
-annotation class ToStringSerialize
\ No newline at end of file
+annotation class ToStringSerialize
+
+abstract class SimpleDeserializer(private val func: JsonParser.() -> T) : JsonDeserializer() {
+ override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): T = func(parser)
+}
diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
index 86c50a4160..9a7c0a8dfb 100644
--- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
@@ -3,17 +3,20 @@ package net.corda.client.jackson
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.IntNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
+import net.corda.client.jackson.internal.childrenAs
import net.corda.client.jackson.internal.valueAs
-import net.corda.core.contracts.Amount
+import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
+import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo
@@ -21,7 +24,10 @@ 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.CoreTransaction
import net.corda.core.transactions.SignedTransaction
+import net.corda.core.transactions.TransactionBuilder
+import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.*
import net.corda.finance.USD
import net.corda.nodeapi.internal.crypto.x509Certificates
@@ -39,10 +45,11 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import java.math.BigInteger
-import java.nio.charset.StandardCharsets.*
+import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
+import java.time.Instant
import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.collections.ArrayList
@@ -52,7 +59,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
private companion object {
val SEED: BigInteger = BigInteger.valueOf(20170922L)
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
- val BOB_PUBKEY = TestIdentity(BOB_NAME, 70).publicKey
+ val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).publicKey
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
@@ -76,6 +83,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
services = rigorousMock()
cordappProvider = rigorousMock()
doReturn(cordappProvider).whenever(services).cordappProvider
+ doReturn(testNetworkParameters()).whenever(services).networkParameters
}
@Test
@@ -182,43 +190,190 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Test
fun TransactionSignature() {
- val metadata = SignatureMetadata(1, 1)
- val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, metadata)
+ val signatureMetadata = SignatureMetadata(1, 1)
+ val partialMerkleTree = PartialMerkleTree(PartialTree.Node(
+ left = PartialTree.Leaf(SecureHash.randomSHA256()),
+ right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
+ ))
+ val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, signatureMetadata, partialMerkleTree)
val json = mapper.valueToTree(transactionSignature)
- val (bytes, by, signatureMetadata, partialMerkleTree) = json.assertHasOnlyFields(
+ val (bytesJson, byJson, signatureMetadataJson, partialMerkleTreeJson) = json.assertHasOnlyFields(
"bytes",
"by",
"signatureMetadata",
"partialMerkleTree"
)
- assertThat(bytes.binaryValue()).isEqualTo(transactionSignature.bytes)
- assertThat(by.valueAs(mapper)).isEqualTo(BOB_PUBKEY)
- assertThat(signatureMetadata.valueAs(mapper)).isEqualTo(metadata)
- assertThat(partialMerkleTree.isNull).isTrue()
+ assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes)
+ assertThat(byJson.valueAs(mapper)).isEqualTo(BOB_PUBKEY)
+ assertThat(signatureMetadataJson.valueAs(mapper)).isEqualTo(signatureMetadata)
+ assertThat(partialMerkleTreeJson.valueAs(mapper).root).isEqualTo(partialMerkleTree.root)
assertThat(mapper.convertValue(json)).isEqualTo(transactionSignature)
}
- // TODO Add test for PartialMerkleTree
-
@Test
- fun SignedTransaction() {
- val attachmentRef = SecureHash.randomSHA256()
- doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
- doReturn(testNetworkParameters()).whenever(services).networkParameters
-
- val stx = makeDummyStx()
+ fun `SignedTransaction (WireTransaction)`() {
+ val attachmentId = SecureHash.randomSHA256()
+ doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
+ val wtx = TransactionBuilder(
+ notary = DUMMY_NOTARY,
+ inputs = mutableListOf(StateRef(SecureHash.randomSHA256(), 1)),
+ attachments = mutableListOf(attachmentId),
+ outputs = mutableListOf(createTransactionState()),
+ commands = mutableListOf(Command(DummyCommandData, listOf(BOB_PUBKEY))),
+ window = TimeWindow.fromStartAndDuration(Instant.now(), 1.hours),
+ privacySalt = net.corda.core.contracts.PrivacySalt()
+ ).toWireTransaction(services)
+ val stx = sign(wtx)
+ partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY)
val json = mapper.valueToTree(stx)
println(mapper.writeValueAsString(json))
- val (txBits, signatures) = json.assertHasOnlyFields("txBits", "signatures")
- assertThat(txBits.binaryValue()).isEqualTo(stx.txBits.bytes)
- val sigs = signatures.elements().asSequence().map { it.valueAs(mapper) }.toList()
- assertThat(sigs).isEqualTo(stx.sigs)
+ val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
+ assertThat(signaturesJson.childrenAs(mapper)).isEqualTo(stx.sigs)
+ val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "privacySalt")
+ assertThat(wtxFields[0].valueAs(mapper)).isEqualTo(wtx.id)
+ assertThat(wtxFields[1].valueAs(mapper)).isEqualTo(wtx.notary)
+ assertThat(wtxFields[2].childrenAs(mapper)).isEqualTo(wtx.inputs)
+ assertThat(wtxFields[3].childrenAs(mapper)).isEqualTo(wtx.attachments)
+ assertThat(wtxFields[4].childrenAs>(mapper)).isEqualTo(wtx.outputs)
+ assertThat(wtxFields[5].childrenAs>(mapper)).isEqualTo(wtx.commands)
+ assertThat(wtxFields[6].valueAs(mapper)).isEqualTo(wtx.timeWindow)
+ assertThat(wtxFields[7].valueAs(mapper)).isEqualTo(wtx.privacySalt)
+ assertThat(mapper.convertValue(wtxJson)).isEqualTo(wtx)
assertThat(mapper.convertValue(json)).isEqualTo(stx)
}
+ @Test
+ fun TransactionState() {
+ val txState = createTransactionState()
+ val json = mapper.valueToTree(txState)
+ println(mapper.writeValueAsString(json))
+ partyObjectMapper.identities += listOf(MINI_CORP.party, DUMMY_NOTARY)
+ assertThat(mapper.convertValue>(json)).isEqualTo(txState)
+ }
+
+ @Test
+ fun Command() {
+ val command = Command(DummyCommandData, listOf(BOB_PUBKEY))
+ val json = mapper.valueToTree(command)
+ assertThat(mapper.convertValue>(json)).isEqualTo(command)
+ }
+
+ @Test
+ fun `TimeWindow - fromOnly`() {
+ val fromOnly = TimeWindow.fromOnly(Instant.now())
+ val json = mapper.valueToTree(fromOnly)
+ assertThat(mapper.convertValue(json)).isEqualTo(fromOnly)
+ }
+
+ @Test
+ fun `TimeWindow - untilOnly`() {
+ val untilOnly = TimeWindow.untilOnly(Instant.now())
+ val json = mapper.valueToTree(untilOnly)
+ assertThat(mapper.convertValue(json)).isEqualTo(untilOnly)
+ }
+
+ @Test
+ fun `TimeWindow - between`() {
+ val between = TimeWindow.between(Instant.now(), Instant.now() + 1.days)
+ val json = mapper.valueToTree(between)
+ assertThat(mapper.convertValue(json)).isEqualTo(between)
+ }
+
+ @Test
+ fun PrivacySalt() {
+ val privacySalt = net.corda.core.contracts.PrivacySalt()
+ val json = mapper.valueToTree(privacySalt)
+ assertThat(json.textValue()).isEqualTo(privacySalt.bytes.toHexString())
+ assertThat(mapper.convertValue(json)).isEqualTo(privacySalt)
+ }
+
+ @Test
+ fun SignatureMetadata() {
+ val signatureMetadata = SignatureMetadata(2, Crypto.ECDSA_SECP256R1_SHA256.schemeNumberID)
+ val json = mapper.valueToTree(signatureMetadata)
+ val (platformVersion, scheme) = json.assertHasOnlyFields("platformVersion", "scheme")
+ assertThat(platformVersion.intValue()).isEqualTo(2)
+ assertThat(scheme.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256")
+ assertThat(mapper.convertValue(json)).isEqualTo(signatureMetadata)
+ }
+
+ @Test
+ fun `SignatureMetadata on unknown schemeNumberID`() {
+ val signatureMetadata = SignatureMetadata(2, Int.MAX_VALUE)
+ val json = mapper.valueToTree(signatureMetadata)
+ assertThat(json["scheme"].intValue()).isEqualTo(Int.MAX_VALUE)
+ assertThat(mapper.convertValue(json)).isEqualTo(signatureMetadata)
+ }
+
+ @Test
+ fun `SignatureScheme serialization`() {
+ val json = mapper.valueToTree(Crypto.ECDSA_SECP256R1_SHA256)
+ assertThat(json.textValue()).isEqualTo("ECDSA_SECP256R1_SHA256")
+ }
+
+ @Test
+ fun `SignatureScheme deserialization`() {
+ assertThat(mapper.convertValue(TextNode("EDDSA_ED25519_SHA512"))).isSameAs(Crypto.EDDSA_ED25519_SHA512)
+ assertThat(mapper.convertValue(IntNode(4))).isSameAs(Crypto.EDDSA_ED25519_SHA512)
+ }
+
+ @Test
+ fun `PartialTree IncludedLeaf`() {
+ val includedLeaf = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
+ val json = mapper.valueToTree(includedLeaf)
+ assertThat(json.assertHasOnlyFields("includedLeaf")[0].textValue()).isEqualTo(includedLeaf.hash.toString())
+ assertThat(mapper.convertValue(json)).isEqualTo(includedLeaf)
+ }
+
+ @Test
+ fun `PartialTree Leaf`() {
+ val leaf = PartialTree.Leaf(SecureHash.randomSHA256())
+ val json = mapper.valueToTree(leaf)
+ assertThat(json.assertHasOnlyFields("leaf")[0].textValue()).isEqualTo(leaf.hash.toString())
+ assertThat(mapper.convertValue(json)).isEqualTo(leaf)
+ }
+
+ @Test
+ fun `simple PartialTree Node`() {
+ val node = PartialTree.Node(
+ left = PartialTree.Leaf(SecureHash.randomSHA256()),
+ right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
+ )
+ val json = mapper.valueToTree(node)
+ println(mapper.writeValueAsString(json))
+ val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right")
+ assertThat(leftJson.valueAs(mapper)).isEqualTo(node.left)
+ assertThat(rightJson.valueAs(mapper)).isEqualTo(node.right)
+ assertThat(mapper.convertValue(json)).isEqualTo(node)
+ }
+
+ @Test
+ fun `complex PartialTree Node`() {
+ val node = PartialTree.Node(
+ left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()),
+ right = PartialTree.Node(
+ left = PartialTree.Leaf(SecureHash.randomSHA256()),
+ right = PartialTree.Leaf(SecureHash.randomSHA256())
+ )
+ )
+ val json = mapper.valueToTree(node)
+ println(mapper.writeValueAsString(json))
+ assertThat(mapper.convertValue(json)).isEqualTo(node)
+ }
+
+ // TODO Issued
+ // TODO PartyAndReference
+
@Test
fun CordaX500Name() {
- testToStringSerialisation(CordaX500Name(commonName = "COMMON", organisationUnit = "ORG UNIT", organisation = "ORG", locality = "NYC", state = "NY", country = "US"))
+ testToStringSerialisation(CordaX500Name(
+ commonName = "COMMON",
+ organisationUnit = "ORG UNIT",
+ organisation = "ORG",
+ locality = "NYC",
+ state = "NY",
+ country = "US"
+ ))
}
@Test
@@ -462,14 +617,36 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
assertThat(mapper.convertValue(json)).isEqualTo(data)
}
- private fun makeDummyStx(): SignedTransaction {
- val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
- .toWireTransaction(services)
+ @Test
+ fun `kotlin object`() {
+ val json = mapper.valueToTree(KotlinObject)
+ assertThat(mapper.convertValue(json)).isSameAs(KotlinObject)
+ }
+
+ @Test
+ fun `@CordaSerializable kotlin object`() {
+ val json = mapper.valueToTree(CordaSerializableKotlinObject)
+ assertThat(mapper.convertValue(json)).isSameAs(CordaSerializableKotlinObject)
+ }
+
+ private fun sign(ctx: CoreTransaction): SignedTransaction {
+ val partialMerkleTree = PartialMerkleTree(PartialTree.Node(
+ left = PartialTree.Leaf(SecureHash.randomSHA256()),
+ right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
+ ))
val signatures = listOf(
- TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)),
+ TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID), partialMerkleTree),
TransactionSignature(ByteArray(1), BOB_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(BOB_PUBKEY).schemeNumberID))
)
- return SignedTransaction(wtx, signatures)
+ return SignedTransaction(ctx, signatures)
+ }
+
+ private fun createTransactionState(): TransactionState {
+ return TransactionState(
+ data = DummyContract.SingleOwnerState(magicNumber = 123, owner = MINI_CORP.party),
+ contract = DummyContract.PROGRAM_ID,
+ notary = DUMMY_NOTARY
+ )
}
private inline fun testToStringSerialisation(value: T) {
@@ -495,6 +672,11 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
val nonCtor: Int get() = value
}
+ private object KotlinObject
+
+ @CordaSerializable
+ private object CordaSerializableKotlinObject
+
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
override var isFullParties: Boolean = false
val identities = ArrayList()
diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
index d396330cb6..43c7c0544e 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -220,7 +220,7 @@ object Crypto {
}
/**
- * Factory pattern to retrieve the corresponding [SignatureScheme] based on the type of the [String] input.
+ * Factory pattern to retrieve the corresponding [SignatureScheme] based on [SignatureScheme.schemeCodeName].
* This function is usually called by key generators and verify signature functions.
* In case the input is not a key in the supportedSignatureSchemes map, null will be returned.
* @param schemeCodeName a [String] that should match a supported signature scheme code name (e.g. ECDSA_SECP256K1_SHA256), see [Crypto].
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
index 0176397c2f..6c1f87ed68 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt
@@ -6,8 +6,9 @@ import java.security.spec.AlgorithmParameterSpec
/**
* This class is used to define a digital signature scheme.
- * @param schemeNumberID we assign a number ID for better efficiency on-wire serialisation. Please ensure uniqueness between schemes.
- * @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
+ * @param schemeNumberID unique number ID for better efficiency on-wire serialisation.
+ * @param schemeCodeName unique code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256,
+ * EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
* @param signatureOID ASN.1 algorithm identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
* @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to
* the same signature scheme.
diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
index 43338d4706..f5a2fdbae1 100644
--- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
@@ -7,12 +7,7 @@ import com.google.common.hash.HashingInputStream
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext
-import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.DigitalSignature
-import net.corda.core.crypto.SecureHash
-import net.corda.core.crypto.SignedData
-import net.corda.core.crypto.sha256
-import net.corda.core.crypto.sign
+import net.corda.core.crypto.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
@@ -37,6 +32,7 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
+import java.lang.reflect.Member
import java.lang.reflect.Modifier
import java.math.BigDecimal
import java.net.HttpURLConnection
@@ -51,23 +47,11 @@ import java.nio.file.Paths
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
-import java.security.cert.CertPath
-import java.security.cert.CertPathValidator
-import java.security.cert.CertPathValidatorException
-import java.security.cert.PKIXCertPathValidatorResult
-import java.security.cert.PKIXParameters
-import java.security.cert.TrustAnchor
-import java.security.cert.X509Certificate
+import java.security.cert.*
import java.time.Duration
import java.time.temporal.Temporal
import java.util.*
-import java.util.Spliterator.DISTINCT
-import java.util.Spliterator.IMMUTABLE
-import java.util.Spliterator.NONNULL
-import java.util.Spliterator.ORDERED
-import java.util.Spliterator.SIZED
-import java.util.Spliterator.SORTED
-import java.util.Spliterator.SUBSIZED
+import java.util.Spliterator.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit
import java.util.stream.IntStream
@@ -311,6 +295,23 @@ fun KClass.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance()
}
+/** Similar to [KClass.objectInstance] but also works on private objects. */
+val Class.kotlinObjectInstance: T? get() {
+ return try {
+ kotlin.objectInstance
+ } catch (_: Throwable) {
+ val field = try { getDeclaredField("INSTANCE") } catch (_: NoSuchFieldException) { null }
+ field?.let {
+ if (it.type == this && it.isPublic && it.isStatic && it.isFinal) {
+ it.isAccessible = true
+ uncheckedCast(it.get(null))
+ } else {
+ null
+ }
+ }
+ }
+}
+
/**
* A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's
* visibility.
@@ -385,6 +386,12 @@ inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifie
inline val Class<*>.isConcreteClass: Boolean get() = !isInterface && !isAbstractClass
+inline val Member.isPublic: Boolean get() = Modifier.isPublic(modifiers)
+
+inline val Member.isStatic: Boolean get() = Modifier.isStatic(modifiers)
+
+inline val Member.isFinal: Boolean get() = Modifier.isFinal(modifiers)
+
fun URI.toPath(): Path = Paths.get(this)
fun URL.toPath(): Path = toURI().toPath()
diff --git a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt
index 0a2fb69f26..21f89c34af 100644
--- a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt
+++ b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt
@@ -1,6 +1,7 @@
package net.corda.core.internal
-import org.assertj.core.api.Assertions
+import net.corda.core.contracts.TimeWindow
+import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.util.stream.IntStream
@@ -8,29 +9,29 @@ import java.util.stream.Stream
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
-class InternalUtilsTest {
+open class InternalUtilsTest {
@Test
fun `noneOrSingle on an empty collection`() {
val collection = emptyList()
- Assertions.assertThat(collection.noneOrSingle()).isNull()
- Assertions.assertThat(collection.noneOrSingle { it == 1 }).isNull()
+ assertThat(collection.noneOrSingle()).isNull()
+ assertThat(collection.noneOrSingle { it == 1 }).isNull()
}
@Test
fun `noneOrSingle on a singleton collection`() {
val collection = listOf(1)
- Assertions.assertThat(collection.noneOrSingle()).isEqualTo(1)
- Assertions.assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1)
- Assertions.assertThat(collection.noneOrSingle { it == 2 }).isNull()
+ assertThat(collection.noneOrSingle()).isEqualTo(1)
+ assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1)
+ assertThat(collection.noneOrSingle { it == 2 }).isNull()
}
@Test
fun `noneOrSingle on a collection with two items`() {
val collection = listOf(1, 2)
assertFailsWith { collection.noneOrSingle() }
- Assertions.assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1)
- Assertions.assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2)
- Assertions.assertThat(collection.noneOrSingle { it == 3 }).isNull()
+ assertThat(collection.noneOrSingle { it == 1 }).isEqualTo(1)
+ assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2)
+ assertThat(collection.noneOrSingle { it == 3 }).isNull()
assertFailsWith { collection.noneOrSingle { it > 0 } }
}
@@ -39,7 +40,7 @@ class InternalUtilsTest {
val collection = listOf(1, 2, 1)
assertFailsWith { collection.noneOrSingle() }
assertFailsWith { collection.noneOrSingle { it == 1 } }
- Assertions.assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2)
+ assertThat(collection.noneOrSingle { it == 2 }).isEqualTo(2)
}
@Test
@@ -88,4 +89,19 @@ class InternalUtilsTest {
assertEquals(Array::class.java, b.javaClass)
assertArrayEquals(arrayOf("one", "two", null), b)
}
+
+ @Test
+ fun kotlinObjectInstance() {
+ assertThat(PublicObject::class.java.kotlinObjectInstance).isSameAs(PublicObject)
+ assertThat(PrivateObject::class.java.kotlinObjectInstance).isSameAs(PrivateObject)
+ assertThat(ProtectedObject::class.java.kotlinObjectInstance).isSameAs(ProtectedObject)
+ assertThat(TimeWindow::class.java.kotlinObjectInstance).isNull()
+ assertThat(PrivateClass::class.java.kotlinObjectInstance).isNull()
+ }
+
+ object PublicObject
+ private object PrivateObject
+ protected object ProtectedObject
+
+ private class PrivateClass
}
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index bce0ddeb4b..3f1ee9d428 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -51,7 +51,10 @@ Unreleased
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.
- * ``SignedTransaction`` is serialised into its ``txBits`` and ``signatures`` and can be deserialised back
+ * ``WireTransaction`` now nicely outputs into its components: ``id``, ``notary``, ``inputs``, ``attachments``, ``outputs``,
+ ``commands``, ``timeWindow`` and ``privacySalt``. This can be deserialised back.
+ * ``SignedTransaction`` is serialised into ``wire`` (i.e. currently only ``WireTransaction`` tested) and ``signatures``,
+ and can be deserialised back.
* ``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``
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
index 7d511ac61c..f3b78acd4e 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
@@ -2,13 +2,9 @@
package net.corda.nodeapi.internal.config
-import com.typesafe.config.Config
-import com.typesafe.config.ConfigException
-import com.typesafe.config.ConfigFactory
-import com.typesafe.config.ConfigUtil
-import com.typesafe.config.ConfigValueFactory
-import com.typesafe.config.ConfigValueType
+import com.typesafe.config.*
import net.corda.core.identity.CordaX500Name
+import net.corda.core.internal.isStatic
import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NetworkHostAndPort
@@ -16,7 +12,6 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
-import java.lang.reflect.Modifier.isStatic
import java.lang.reflect.ParameterizedType
import java.net.Proxy
import java.net.URL
@@ -209,7 +204,7 @@ fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig(
private fun Any.toConfigMap(): Map {
val values = HashMap()
for (field in javaClass.declaredFields) {
- if (isStatic(field.modifiers) || field.isSynthetic) continue
+ if (field.isStatic || field.isSynthetic) continue
field.isAccessible = true
val value = field.get(this) ?: continue
val configValue = if (value is String || value is Boolean || value is Number) {
diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt
index 58e8b61c06..e9ce3e4d56 100644
--- a/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt
+++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/CordaClassResolver.kt
@@ -6,6 +6,7 @@ import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.Util
+import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.writer
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
@@ -15,7 +16,6 @@ import net.corda.serialization.internal.MutableClassWhitelist
import net.corda.serialization.internal.TransientClassWhiteList
import net.corda.serialization.internal.amqp.hasCordaSerializable
import java.io.PrintWriter
-import java.lang.reflect.Modifier
import java.lang.reflect.Modifier.isAbstract
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Paths
@@ -75,22 +75,7 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl
override fun registerImplicit(type: Class<*>): Registration {
val targetType = typeForSerializationOf(type)
- // Is this a Kotlin object? We use our own reflection here rather than .kotlin.objectInstance because Kotlin
- // reflection won't work for private objects, and can throw exceptions in other circumstances as well.
- val objectInstance = try {
- targetType.declaredFields.singleOrNull {
- it.name == "INSTANCE" &&
- it.type == type &&
- Modifier.isStatic(it.modifiers) &&
- Modifier.isFinal(it.modifiers) &&
- Modifier.isPublic(it.modifiers)
- }?.let {
- it.isAccessible = true
- type.cast(it.get(null)!!)
- }
- } catch (t: Throwable) {
- null
- }
+ val objectInstance = targetType.kotlinObjectInstance
// We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent.
val references = kryo.references
diff --git a/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt b/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt
index d26aaeca06..d0cd3fa79f 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt
@@ -1,7 +1,7 @@
package net.corda.node.utilities
+import net.corda.core.internal.isStatic
import java.lang.reflect.Method
-import java.lang.reflect.Modifier
import java.lang.reflect.Type
import java.time.Instant
@@ -131,7 +131,7 @@ object ObjectDiffer {
private fun getFieldFoci(obj: Any) : List {
val foci = ArrayList()
for (method in obj.javaClass.declaredMethods) {
- if (Modifier.isStatic(method.modifiers)) {
+ if (method.isStatic) {
continue
}
if (method.name.startsWith("get") && method.name.length > 3 && method.parameterCount == 0) {
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt
index 36c63ace7b..188ce947a2 100644
--- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt
@@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.hash.Hasher
import com.google.common.hash.Hashing
+import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toBase64
import java.io.NotSerializableException
@@ -153,7 +154,7 @@ class SerializerFingerPrinter : FingerPrinter {
}.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH)
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) {
- if (type.objectInstance() != null) {
+ if (type.kotlinObjectInstance != null) {
// TODO: name collision is too likely for kotlin objects, we need to introduce some reference
// to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt
index 47b5387413..36bc35d147 100644
--- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt
@@ -3,6 +3,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken
import net.corda.core.internal.isConcreteClass
+import net.corda.core.internal.isPublic
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
@@ -155,7 +156,7 @@ fun Class.propertyDescriptors(): Map {
// In addition, only getters that take zero parameters and setters that take a single
// parameter will be considered
clazz!!.declaredMethods?.map { func ->
- if (!Modifier.isPublic(func.modifiers)) return@map
+ if (!func.isPublic) return@map
if (func.name == "getClass") return@map
PropertyDescriptorsRegex.re.find(func.name)?.apply {
@@ -537,49 +538,3 @@ fun hasCordaSerializable(type: Class<*>): Boolean {
|| type.interfaces.any(::hasCordaSerializable)
|| (type.superclass != null && hasCordaSerializable(type.superclass))
}
-
-/**
- * By default use Kotlin reflection and grab the objectInstance. However, that doesn't play nicely with nested
- * private objects. Even setting the accessibility override (setAccessible) still causes an
- * [IllegalAccessException] when attempting to retrieve the value of the INSTANCE field.
- *
- * Whichever reference to the class Kotlin reflection uses, override (set from setAccessible) on that field
- * isn't set even when it was explicitly set as accessible before calling into the kotlin reflection routines.
- *
- * For example
- *
- * clazz.getDeclaredField("INSTANCE")?.apply {
- * isAccessible = true
- * kotlin.objectInstance // This throws as the INSTANCE field isn't accessible
- * }
- *
- * Therefore default back to good old java reflection and simply look for the INSTANCE field as we are never going
- * to serialize a companion object.
- *
- * As such, if objectInstance fails access, revert to Java reflection and try that
- */
-fun Class<*>.objectInstance(): Any? {
- return try {
- this.kotlin.objectInstance
- } catch (e: IllegalAccessException) {
- // Check it really is an object (i.e. it has no constructor)
- if (constructors.isNotEmpty()) null
- else {
- try {
- this.getDeclaredField("INSTANCE")?.let { field ->
- // and must be marked as both static and final (>0 means they're set)
- if (modifiers and Modifier.STATIC == 0 || modifiers and Modifier.FINAL == 0) null
- else {
- val accessibility = field.isAccessible
- field.isAccessible = true
- val obj = field.get(null)
- field.isAccessible = accessibility
- obj
- }
- }
- } catch (e: NoSuchFieldException) {
- null
- }
- }
- }
-}
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt
index eb22bab382..560ddb998d 100644
--- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt
@@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeResolver
+import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.utilities.debug
@@ -329,7 +330,7 @@ open class SerializerFactory(
if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this)
else ArraySerializer.make(type, this)
} else {
- val singleton = clazz.objectInstance()
+ val singleton = clazz.kotlinObjectInstance
if (singleton != null) {
whitelist.requireWhitelisted(clazz)
SingletonSerializer(clazz, singleton, this)