diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 988d6e4a25..9208ede561 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -106,10 +106,6 @@
-
-
-
-
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 e56711a50d..6eef15668a 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
@@ -38,10 +38,7 @@ 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
-import net.corda.serialization.internal.amqp.hasCordaSerializable
-import net.corda.serialization.internal.amqp.propertiesForSerialization
+import net.corda.serialization.internal.amqp.*
import java.math.BigDecimal
import java.security.PublicKey
import java.security.cert.CertPath
@@ -88,7 +85,7 @@ class CordaModule : SimpleModule("corda-core") {
*/
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
// 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)
+ private val serializerFactory = SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader)
override fun changeProperties(config: SerializationConfig,
beanDesc: BeanDescription,
diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt
index 3ef215f9e8..6e094e87ee 100644
--- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt
+++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt
@@ -8,10 +8,7 @@ import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.serialization.internal.*
-import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
-import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
-import net.corda.serialization.internal.amqp.SerializerFactory
-import net.corda.serialization.internal.amqp.amqpMagic
+import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
/**
@@ -52,7 +49,7 @@ class AMQPClientSerializationScheme(
}
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
- return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
+ return SerializerFactoryBuilder.build(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
register(RpcClientObservableDeSerializer)
register(RpcClientCordaFutureSerializer(this))
register(RxNotificationSerializer(this))
diff --git a/constants.properties b/constants.properties
index 9ed2417171..d0817917ec 100644
--- a/constants.properties
+++ b/constants.properties
@@ -1,4 +1,4 @@
-gradlePluginsVersion=4.0.33
+gradlePluginsVersion=4.0.34
kotlinVersion=1.2.71
# ***************************************************************#
# When incrementing platformVersion make sure to update #
diff --git a/core/src/main/kotlin/net/corda/core/CordaOID.kt b/core/src/main/kotlin/net/corda/core/CordaOID.kt
index 644ade5fef..0a6bf3b658 100644
--- a/core/src/main/kotlin/net/corda/core/CordaOID.kt
+++ b/core/src/main/kotlin/net/corda/core/CordaOID.kt
@@ -1,18 +1,23 @@
package net.corda.core
+import net.corda.core.crypto.internal.AliasPrivateKey
+
/**
- * OIDs used for the Corda platform. Entries MUST NOT be removed from this file; if an OID is incorrectly assigned it
- * should be marked deprecated.
+ * OIDs used for the Corda platform. All entries MUST be defined in this file only and they MUST NOT be removed.
+ * If an OID is incorrectly assigned, it should be marked deprecated and NEVER be reused again.
*/
@KeepForDJVM
object CordaOID {
/** Assigned to R3, see http://www.oid-info.com/cgi-bin/display?oid=1.3.6.1.4.1.50530&action=display */
const val R3_ROOT = "1.3.6.1.4.1.50530"
- /** OIDs issued for the Corda platform */
+ /** OIDs issued for the Corda platform. */
const val CORDA_PLATFORM = "$R3_ROOT.1"
/**
* Identifier for the X.509 certificate extension specifying the Corda role. See
* https://r3-cev.atlassian.net/wiki/spaces/AWG/pages/156860572/Certificate+identity+type+extension for details.
*/
const val X509_EXTENSION_CORDA_ROLE = "$CORDA_PLATFORM.1"
-}
\ No newline at end of file
+
+ /** OID for [AliasPrivateKey]. */
+ const val ALIAS_PRIVATE_KEY = "$CORDA_PLATFORM.2"
+}
diff --git a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
index e58fbd39ad..61c611076e 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt
@@ -42,7 +42,9 @@ private fun provideNonDeterministic(provider: Provider) {
@KeepForDJVM
object CordaObjectIdentifier {
// UUID-based OID
- // TODO: Register for an OID space and issue our own shorter OID.
+ // TODO define and use an official Corda OID in [CordaOID]. We didn't do yet for backwards compatibility purposes,
+ // because key.encoded (serialised version of keys) and [PublicKey.hash] for already stored [CompositeKey]s
+ // will not match.
@JvmField
val COMPOSITE_KEY = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
@JvmField
diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt
index 50d75fdb31..ee872bf72b 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt
@@ -1,5 +1,6 @@
package net.corda.core.crypto.internal
+import net.corda.core.CordaOID
import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
@@ -17,12 +18,7 @@ import java.security.spec.PKCS8EncodedKeySpec
data class AliasPrivateKey(val alias: String): PrivateKey {
companion object {
- // UUID-based OID
- // TODO: Register for an OID space and issue our own shorter OID.
- @JvmField
- val ALIAS_PRIVATE_KEY = ASN1ObjectIdentifier("2.26.40086077608615255153862931087626791001")
-
- const val ALIAS_KEY_ALGORITHM = "ALIAS"
+ const val ALIAS_KEY_ALGORITHM = "AliasPrivateKey"
}
override fun getAlgorithm() = ALIAS_KEY_ALGORITHM
@@ -30,7 +26,10 @@ data class AliasPrivateKey(val alias: String): PrivateKey {
override fun getEncoded(): ByteArray {
val keyVector = ASN1EncodableVector()
keyVector.add(DERUTF8String(alias))
- val privateKeyInfoBytes = PrivateKeyInfo(AlgorithmIdentifier(ALIAS_PRIVATE_KEY), DERSequence(keyVector)).getEncoded(ASN1Encoding.DER)
+ val privateKeyInfoBytes = PrivateKeyInfo(
+ AlgorithmIdentifier(ASN1ObjectIdentifier(CordaOID.ALIAS_PRIVATE_KEY)),
+ DERSequence(keyVector)
+ ).getEncoded(ASN1Encoding.DER)
val keySpec = PKCS8EncodedKeySpec(privateKeyInfoBytes)
return keySpec.encoded
}
diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt
index 59b2801bb1..6ee97c6031 100644
--- a/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt
+++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionVerificationExceptionSerialisationTests.kt
@@ -6,7 +6,7 @@ import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.SerializationOutput
-import net.corda.serialization.internal.amqp.SerializerFactory
+import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.serialization.internal.amqp.custom.PublicKeySerializer
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
@@ -15,7 +15,7 @@ import org.junit.Test
import kotlin.test.assertEquals
class TransactionVerificationExceptionSerialisationTests {
- private fun defaultFactory() = SerializerFactory(
+ private fun defaultFactory() = SerializerFactoryBuilder.build(
AllWhitelist,
ClassLoader.getSystemClassLoader()
)
diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst
index 69c25beccf..c5eb836fe5 100644
--- a/docs/source/corda-configuration-file.rst
+++ b/docs/source/corda-configuration-file.rst
@@ -320,6 +320,8 @@ absolute path to the node's base directory.
Valid values for this property are between 4 (that is the number used for the single threaded state machine in
open source) and the number of flow threads.
+.. _corda_configuration_file_signer_blacklist:
+
:cordappSignerKeyFingerprintBlacklist: List of public keys fingerprints (SHA-256 of public key hash) not allowed as Cordapp JARs signers.
Node will not load Cordapps signed by those keys.
The option takes effect only in production mode and defaults to Corda development keys (``["56CA54E803CB87C8472EBD3FBC6A2F1876E814CEEBF74860BD46997F40729367",
diff --git a/docs/source/cordapp-build-systems.rst b/docs/source/cordapp-build-systems.rst
index 8c39aa27be..6f5e289fff 100644
--- a/docs/source/cordapp-build-systems.rst
+++ b/docs/source/cordapp-build-systems.rst
@@ -162,6 +162,9 @@ without need to create a keystore and configure the ``cordapp`` plugin.
For production deployment ensure to sign the CorDapp using your own certificate e.g. by setting system properties to point to an external keystore
or by disabling signing in ``cordapp`` plugin and signing the CordDapp JAR downstream in your build pipeline.
CorDapp signed by Corda development certificate is accepted by Corda node only when running in the development mode.
+In case CordDapp signed by the (default) development key is run on node in the production mode (e.g. for testing),
+the node may be set to accept the development key by adding the ``cordappSignerKeyFingerprintBlacklist = []`` property set to empty list
+(see :ref:`Configuring a node `).
Signing options can be contextually overwritten by the relevant system properties as described above.
This allows the single ``build.gradle`` file to be used for a development build (defaulting to the Corda development keystore)
diff --git a/docs/source/flow-overriding.rst b/docs/source/flow-overriding.rst
index 273ff7fa03..7c12b1d471 100644
--- a/docs/source/flow-overriding.rst
+++ b/docs/source/flow-overriding.rst
@@ -138,4 +138,63 @@ the ``SubResponder`` with ``BaseResponder``
flowOverride("net.corda.Initiator", "net.corda.BaseResponder")
}
-This will generate the corresponding ``flowOverrides`` section and place it in the configuration for that node.
\ No newline at end of file
+This will generate the corresponding ``flowOverrides`` section and place it in the configuration for that node.
+
+Modifying the behaviour of @InitiatingFlow(s)
+---------------------------------------------
+
+It is likely that initiating flows will also require changes to reflect the different systems that are likely to be encountered.
+At the moment, corda provides the ability to subclass an Initiator, and ensures that the correct responder will be invoked.
+In the below example, we will change the behaviour of an Initiator from filtering Notaries out from comms, to only communicating with Notaries
+
+ .. code-block:: kotlin
+
+ @InitiatingFlow
+ @StartableByRPC
+ @StartableByService
+ open class BaseInitiator : FlowLogic() {
+ @Suspendable
+ override fun call(): String {
+ val partiesToTalkTo = serviceHub.networkMapCache.allNodes
+ .filterNot { it.legalIdentities.first() in serviceHub.networkMapCache.notaryIdentities }
+ .filterNot { it.legalIdentities.first().name == ourIdentity.name }.map { it.legalIdentities.first() }
+ val responses = ArrayList()
+ for (party in partiesToTalkTo) {
+ val session = initiateFlow(party)
+ val received = session.receive().unwrap { it }
+ responses.add(party.name.toString() + " responded with backend: " + received)
+ }
+ return "${getFLowName()} received the following \n" + responses.joinToString("\n") { it }
+ }
+
+ open fun getFLowName(): String {
+ return "Normal Computer"
+ }
+ }
+
+ @StartableByRPC
+ @StartableByService
+ class NotaryOnlyInitiator : BaseInitiator() {
+ @Suspendable
+ override fun call(): String {
+ return "Notary Communicator received:\n" + serviceHub.networkMapCache.notaryIdentities.map {
+ "Notary: ${it.name.organisation} is using a " + initiateFlow(it).receive().unwrap { it }
+ }.joinToString("\n") { it }
+ }
+
+.. warning:: The subclass must not have the @InitiatingFlow annotation.
+
+Corda will use the first annotation detected in the class hierarchy to determine which responder should be invoked. So for a Responder similar to
+
+ .. code-block:: kotlin
+
+ @InitiatedBy(BaseInitiator::class)
+ class BobbyResponder(othersideSession: FlowSession) : BaseResponder(othersideSession) {
+ override fun getMessageFromBackend(): String {
+ return "Robert'); DROP TABLE STATES;"
+ }
+ }
+
+it would be possible to invoke either ``BaseInitiator`` or ``NotaryOnlyInitiator`` and ``BobbyResponder`` would be used to reply.
+
+.. warning:: You must ensure the sequence of sends/receives/subFlows in a subclass are compatible with the parent.
\ No newline at end of file
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
index ff94acacae..c3a7fd71e9 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
@@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.network
import com.typesafe.config.Config
+import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
@@ -30,6 +31,7 @@ import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.amqpMagic
+import java.io.File
import java.io.InputStream
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
@@ -96,21 +98,36 @@ internal constructor(private val initSerEnv: Boolean,
private fun generateNodeInfo(nodeDir: Path): Path {
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
+ val nodeInfoGenFile = (logsDir / "node-info-gen.log").toFile()
val process = ProcessBuilder(nodeInfoGenCmd)
.directory(nodeDir.toFile())
.redirectErrorStream(true)
- .redirectOutput((logsDir / "node-info-gen.log").toFile())
+ .redirectOutput(nodeInfoGenFile)
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
.start()
if (!process.waitFor(3, TimeUnit.MINUTES)) {
process.destroyForcibly()
- throw IllegalStateException("Error while generating node info file. Please check the logs in $logsDir.")
+ printNodeInfoGenLogToConsole(nodeInfoGenFile)
}
- check(process.exitValue() == 0) { "Error while generating node info file. Please check the logs in $logsDir." }
+ printNodeInfoGenLogToConsole(nodeInfoGenFile) { process.exitValue() == 0 }
return nodeDir.list { paths ->
paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
}
}
+
+ private fun printNodeInfoGenLogToConsole(nodeInfoGenFile: File, check: (() -> Boolean) = { true }) {
+ if (!check.invoke()) {
+ val nodeDir = nodeInfoGenFile.parent
+ val nodeIdentifier = try {
+ ConfigFactory.parseFile((nodeDir / "node.conf").toFile()).getString("myLegalName")
+ } catch (e: ConfigException) {
+ nodeDir
+ }
+ System.err.println("#### Error while generating node info file $nodeIdentifier ####")
+ nodeInfoGenFile.inputStream().copyTo(System.err)
+ throw IllegalStateException("Error while generating node info file. Please check the logs in $nodeDir.")
+ }
+ }
}
sealed class NotaryCluster {
@@ -142,7 +159,7 @@ internal constructor(private val initSerEnv: Boolean,
private fun isBFTNotary(config: Config): Boolean {
// TODO: pass a commandline parameter to the bootstrapper instead. Better yet, a notary config map
// specifying the notary identities and the type (single-node, CFT, BFT) of each notary to set up.
- return config.getString ("notary.className").contains("BFT", true)
+ return config.getString("notary.className").contains("BFT", true)
}
private fun generateServiceIdentitiesForNotaryClusters(configs: Map) {
@@ -172,7 +189,7 @@ internal constructor(private val initSerEnv: Boolean,
}
/** Entry point for the tool */
- fun bootstrap(directory: Path, copyCordapps: Boolean, minimumPlatformVersion: Int, packageOwnership : Map = emptyMap()) {
+ fun bootstrap(directory: Path, copyCordapps: Boolean, minimumPlatformVersion: Int, packageOwnership: Map = emptyMap()) {
require(minimumPlatformVersion <= PLATFORM_VERSION) { "Minimum platform version cannot be greater than $PLATFORM_VERSION" }
// Don't accidently include the bootstrapper jar as a CorDapp!
val bootstrapperJar = javaClass.location.toPath()
@@ -188,7 +205,7 @@ internal constructor(private val initSerEnv: Boolean,
copyCordapps: Boolean,
fromCordform: Boolean,
minimumPlatformVersion: Int = PLATFORM_VERSION,
- packageOwnership : Map = emptyMap()
+ packageOwnership: Map = emptyMap()
) {
directory.createDirectories()
println("Bootstrapping local test network in $directory")
@@ -361,21 +378,20 @@ internal constructor(private val initSerEnv: Boolean,
existingNetParams: NetworkParameters?,
nodeDirs: List,
minimumPlatformVersion: Int,
- packageOwnership : Map
+ packageOwnership: Map
): NetworkParameters {
// TODO Add config for maxMessageSize and maxTransactionSize
val netParams = if (existingNetParams != null) {
if (existingNetParams.whitelistedContractImplementations == whitelist && existingNetParams.notaries == notaryInfos &&
- existingNetParams.packageOwnership.entries.containsAll(packageOwnership.entries)) {
+ existingNetParams.packageOwnership.entries.containsAll(packageOwnership.entries)) {
existingNetParams
} else {
- var updatePackageOwnership = mutableMapOf(*existingNetParams.packageOwnership.map { Pair(it.key,it.value) }.toTypedArray())
+ var updatePackageOwnership = mutableMapOf(*existingNetParams.packageOwnership.map { Pair(it.key, it.value) }.toTypedArray())
packageOwnership.forEach { key, value ->
if (value == null) {
if (updatePackageOwnership.remove(key) != null)
println("Unregistering package $key")
- }
- else {
+ } else {
if (updatePackageOwnership.put(key, value) == null)
println("Registering package $key for owner ${value.toStringShort()}")
}
diff --git a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt
index dfe6b6fafd..1cb164b0eb 100644
--- a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt
+++ b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt
@@ -8,6 +8,7 @@ import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory
+import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
/**
@@ -28,7 +29,7 @@ class AMQPServerSerializationScheme(
}
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
- return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
+ return SerializerFactoryBuilder.build(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
register(RpcServerObservableSerializer())
register(RpcServerCordaFutureSerializer(this))
register(RxNotificationSerializer(this))
diff --git a/node/src/test/kotlin/net/corda/node/internal/serialization/RpcServerObservableSerializerTests.kt b/node/src/test/kotlin/net/corda/node/internal/serialization/RpcServerObservableSerializerTests.kt
index 720d147eac..a12cf73042 100644
--- a/node/src/test/kotlin/net/corda/node/internal/serialization/RpcServerObservableSerializerTests.kt
+++ b/node/src/test/kotlin/net/corda/node/internal/serialization/RpcServerObservableSerializerTests.kt
@@ -10,7 +10,7 @@ import net.corda.node.serialization.amqp.RpcServerObservableSerializer
import net.corda.node.services.messaging.ObservableSubscription
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.SerializationOutput
-import net.corda.serialization.internal.amqp.SerializerFactory
+import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.Test
import rx.Observable
@@ -34,7 +34,7 @@ class RpcServerObservableSerializerTests {
@Test
fun canSerializerBeRegistered() {
- val sf = SerializerFactory(AllWhitelist, javaClass.classLoader)
+ val sf = SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader)
try {
sf.register(RpcServerObservableSerializer())
@@ -67,7 +67,7 @@ class RpcServerObservableSerializerTests {
deduplicationIdentity = "thisIsATest",
clientAddress = SimpleString(testClientAddress))
- val sf = SerializerFactory(AllWhitelist, javaClass.classLoader).apply {
+ val sf = SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).apply {
register(RpcServerObservableSerializer())
}
diff --git a/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt b/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt
index d17755fa4c..7a803cb661 100644
--- a/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt
+++ b/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt
@@ -12,6 +12,7 @@ import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
+import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
/**
@@ -28,13 +29,13 @@ class AMQPRoundTripRPCSerializationScheme(
cordappCustomSerializers, serializerFactoriesForContexts
) {
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
- return SerializerFactory(AllWhitelist, javaClass.classLoader).apply {
+ return SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).apply {
register(RpcClientObservableDeSerializer)
}
}
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
- return SerializerFactory(AllWhitelist, javaClass.classLoader).apply {
+ return SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).apply {
register(RpcServerObservableSerializer())
}
}
diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle
index 74190fd20a..36242351b5 100644
--- a/serialization-deterministic/build.gradle
+++ b/serialization-deterministic/build.gradle
@@ -50,6 +50,7 @@ task patchSerialization(type: Zip, dependsOn: serializationJarTask) {
exclude 'net/corda/serialization/internal/amqp/AMQPSerializerFactories*'
exclude 'net/corda/serialization/internal/amqp/AMQPStreams*'
exclude 'net/corda/serialization/internal/amqp/AMQPSerializationThreadContext*'
+ exclude 'net/corda/serialization/internal/model/DefaultCacheProvider*'
}
reproducibleFileOrder = true
diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt
index e3f74f2db9..d0bb4b4798 100644
--- a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt
+++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt
@@ -15,15 +15,9 @@ fun createSerializerFactoryFactory(): SerializerFactoryFactory = DeterministicSe
private class DeterministicSerializerFactoryFactory : SerializerFactoryFactory {
override fun make(context: SerializationContext) =
- SerializerFactory(
- whitelist = context.whitelist,
- classCarpenter = DummyClassCarpenter(context.whitelist, context.deserializationClassLoader),
- serializersByType = mutableMapOf(),
- serializersByDescriptor = mutableMapOf(),
- customSerializers = ArrayList(),
- customSerializersCache = mutableMapOf(),
- transformsCache = mutableMapOf()
- )
+ SerializerFactoryBuilder.build(
+ whitelist = context.whitelist,
+ classCarpenter = DummyClassCarpenter(context.whitelist, context.deserializationClassLoader))
}
private class DummyClassCarpenter(
diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/model/DefaultCacheProvider.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/model/DefaultCacheProvider.kt
new file mode 100644
index 0000000000..022c045e87
--- /dev/null
+++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/model/DefaultCacheProvider.kt
@@ -0,0 +1,9 @@
+package net.corda.serialization.internal.model
+
+/**
+ * We can't have [ConcurrentHashMap]s in the DJVM, so it must supply its own version of this object which returns
+ * plain old [MutableMap]s instead.
+ */
+object DefaultCacheProvider {
+ fun createCache(): MutableMap = mutableMapOf()
+}
\ No newline at end of file
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt
index aacde49dac..93e82dfe0a 100644
--- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt
@@ -3,11 +3,14 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext
+import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
fun createSerializerFactoryFactory(): SerializerFactoryFactory = SerializerFactoryFactoryImpl()
open class SerializerFactoryFactoryImpl : SerializerFactoryFactory {
override fun make(context: SerializationContext): SerializerFactory {
- return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled)
+ return SerializerFactoryBuilder.build(context.whitelist,
+ ClassCarpenterImpl(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled)
+ )
}
}
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 c8d4b14891..1cfa23f1eb 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
@@ -1,7 +1,6 @@
package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives
-import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.internal.kotlinObjectInstance
@@ -12,12 +11,11 @@ import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.serialization.internal.carpenter.*
+import net.corda.serialization.internal.model.DefaultCacheProvider
import org.apache.qpid.proton.amqp.*
import java.io.NotSerializableException
import java.lang.reflect.*
import java.util.*
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.CopyOnWriteArrayList
import javax.annotation.concurrent.ThreadSafe
@KeepForDJVM
@@ -49,54 +47,122 @@ data class CustomSerializersCacheKey(val clazz: Class<*>, val declaredType: Type
// TODO: need to support super classes as well as interfaces with our current code base... what's involved? If we continue to ban, what is the impact?
@KeepForDJVM
@ThreadSafe
-open class SerializerFactory(
- val whitelist: ClassWhitelist,
- val classCarpenter: ClassCarpenter,
- private val evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
- val fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
- private val serializersByType: MutableMap>,
- val serializersByDescriptor: MutableMap>,
- private val customSerializers: MutableList,
- private val customSerializersCache: MutableMap?>,
- val transformsCache: MutableMap>>,
+interface SerializerFactory {
+ val whitelist: ClassWhitelist
+ val classCarpenter: ClassCarpenter
+ val fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter
+ // Caches
+ val serializersByType: MutableMap>
+ val serializersByDescriptor: MutableMap>
+ val transformsCache: MutableMap>>
+ val fingerPrinter: FingerPrinter
+ val classloader: ClassLoader
+ /**
+ * Look up, and manufacture if necessary, a serializer for the given type.
+ *
+ * @param actualClass Will be null if there isn't an actual object instance available (e.g. for
+ * restricted type processing).
+ */
+ @Throws(NotSerializableException::class)
+ fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer
+
+ /**
+ * Lookup and manufacture a serializer for the given AMQP type descriptor, assuming we also have the necessary types
+ * contained in the [Schema].
+ */
+ @Throws(NotSerializableException::class)
+ fun get(typeDescriptor: Any, schema: SerializationSchemas): AMQPSerializer
+
+ /**
+ * Register a custom serializer for any type that cannot be serialized or deserialized by the default serializer
+ * that expects to find getters and a constructor with a parameter for each property.
+ */
+ fun register(customSerializer: CustomSerializer)
+
+ fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer?
+ fun registerExternal(customSerializer: CorDappCustomSerializer)
+ fun registerByDescriptor(name: Symbol, serializerCreator: () -> AMQPSerializer): AMQPSerializer
+
+ object AnyType : WildcardType {
+ override fun getUpperBounds(): Array = arrayOf(Object::class.java)
+
+ override fun getLowerBounds(): Array = emptyArray()
+
+ override fun toString(): String = "?"
+ }
+
+ companion object {
+ fun isPrimitive(type: Type): Boolean = primitiveTypeName(type) != null
+
+ fun primitiveTypeName(type: Type): String? {
+ val clazz = type as? Class<*> ?: return null
+ return primitiveTypeNames[Primitives.unwrap(clazz)]
+ }
+
+ fun primitiveType(type: String): Class<*>? {
+ return namesOfPrimitiveTypes[type]
+ }
+
+ private val primitiveTypeNames: Map, String> = mapOf(
+ Character::class.java to "char",
+ Char::class.java to "char",
+ Boolean::class.java to "boolean",
+ Byte::class.java to "byte",
+ UnsignedByte::class.java to "ubyte",
+ Short::class.java to "short",
+ UnsignedShort::class.java to "ushort",
+ Int::class.java to "int",
+ UnsignedInteger::class.java to "uint",
+ Long::class.java to "long",
+ UnsignedLong::class.java to "ulong",
+ Float::class.java to "float",
+ Double::class.java to "double",
+ Decimal32::class.java to "decimal32",
+ Decimal64::class.java to "decimal62",
+ Decimal128::class.java to "decimal128",
+ Date::class.java to "timestamp",
+ UUID::class.java to "uuid",
+ ByteArray::class.java to "binary",
+ String::class.java to "string",
+ Symbol::class.java to "symbol")
+
+ private val namesOfPrimitiveTypes: Map> = primitiveTypeNames.map { it.value to it.key }.toMap()
+
+ fun nameForType(type: Type): String = when (type) {
+ is Class<*> -> {
+ primitiveTypeName(type) ?: if (type.isArray) {
+ "${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}"
+ } else type.name
+ }
+ is ParameterizedType -> {
+ "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
+ }
+ is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
+ is WildcardType -> "?"
+ is TypeVariable<*> -> "?"
+ else -> throw AMQPNotSerializableException(type, "Unable to render type $type to a string.")
+ }
+ }
+}
+
+open class DefaultSerializerFactory(
+ override val whitelist: ClassWhitelist,
+ override val classCarpenter: ClassCarpenter,
+ private val evolutionSerializerProvider: EvolutionSerializerProvider,
+ override val fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter,
private val onlyCustomSerializers: Boolean = false
-) {
- @DeleteForDJVM
- constructor(whitelist: ClassWhitelist,
- classCarpenter: ClassCarpenter,
- evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
- fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
- onlyCustomSerializers: Boolean = false
- ) : this(
- whitelist,
- classCarpenter,
- evolutionSerializerProvider,
- fingerPrinterConstructor,
- ConcurrentHashMap(),
- ConcurrentHashMap(),
- CopyOnWriteArrayList(),
- ConcurrentHashMap(),
- ConcurrentHashMap(),
- onlyCustomSerializers
- )
+) : SerializerFactory {
- @DeleteForDJVM
- constructor(whitelist: ClassWhitelist,
- carpenterClassLoader: ClassLoader,
- lenientCarpenter: Boolean = false,
- evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
- fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
- onlyCustomSerializers: Boolean = false
- ) : this(
- whitelist,
- ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter),
- evolutionSerializerProvider,
- fingerPrinterConstructor,
- onlyCustomSerializers)
+ // Caches
+ override val serializersByType: MutableMap> = DefaultCacheProvider.createCache()
+ override val serializersByDescriptor: MutableMap> = DefaultCacheProvider.createCache()
+ private var customSerializers: List = emptyList()
+ private val customSerializersCache: MutableMap?> = DefaultCacheProvider.createCache()
+ override val transformsCache: MutableMap>> = DefaultCacheProvider.createCache()
- val fingerPrinter by lazy { fingerPrinterConstructor(this) }
+ override val fingerPrinter by lazy { fingerPrinterConstructor(this) }
- val classloader: ClassLoader get() = classCarpenter.classloader
+ override val classloader: ClassLoader get() = classCarpenter.classloader
// Used to short circuit any computation for a given input, for performance.
private data class MemoType(val actualClass: Class<*>?, val declaredType: Type) : Type
@@ -108,7 +174,7 @@ open class SerializerFactory(
* restricted type processing).
*/
@Throws(NotSerializableException::class)
- fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer {
+ override fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer {
// can be useful to enable but will be *extremely* chatty if you do
logger.trace { "Get Serializer for $actualClass ${declaredType.typeName}" }
@@ -169,7 +235,7 @@ open class SerializerFactory(
* contained in the [Schema].
*/
@Throws(NotSerializableException::class)
- fun get(typeDescriptor: Any, schema: SerializationSchemas): AMQPSerializer {
+ override fun get(typeDescriptor: Any, schema: SerializationSchemas): AMQPSerializer {
return serializersByDescriptor[typeDescriptor] ?: {
logger.trace("get Serializer descriptor=${typeDescriptor}")
processSchema(FactorySchemaAndDescriptor(schema, typeDescriptor))
@@ -182,7 +248,7 @@ open class SerializerFactory(
* Register a custom serializer for any type that cannot be serialized or deserialized by the default serializer
* that expects to find getters and a constructor with a parameter for each property.
*/
- open fun register(customSerializer: CustomSerializer) {
+ override fun register(customSerializer: CustomSerializer) {
logger.trace("action=\"Registering custom serializer\", class=\"${customSerializer.type}\"")
if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) {
customSerializers += customSerializer
@@ -193,7 +259,7 @@ open class SerializerFactory(
}
}
- fun registerExternal(customSerializer: CorDappCustomSerializer) {
+ override fun registerExternal(customSerializer: CorDappCustomSerializer) {
logger.trace("action=\"Registering external serializer\", class=\"${customSerializer.type}\"")
if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) {
customSerializers += customSerializer
@@ -319,7 +385,7 @@ open class SerializerFactory(
throw AMQPNotSerializableException(
type,
"Serializer does not support synthetic classes")
- } else if (isPrimitive(clazz)) {
+ } else if (SerializerFactory.isPrimitive(clazz)) {
AMQPPrimitiveSerializer(clazz)
} else {
findCustomSerializer(clazz, declaredType) ?: run {
@@ -344,7 +410,7 @@ open class SerializerFactory(
}
}
- internal fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? {
+ override fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? {
return customSerializersCache.computeIfAbsent(CustomSerializersCacheKey(clazz, declaredType), ::doFindCustomSerializer)
}
@@ -383,69 +449,11 @@ open class SerializerFactory(
return MapSerializer(declaredType, this)
}
- fun registerByDescriptor(name: Symbol, serializerCreator: () -> AMQPSerializer): AMQPSerializer =
+ override fun registerByDescriptor(name: Symbol, serializerCreator: () -> AMQPSerializer): AMQPSerializer =
serializersByDescriptor.computeIfAbsent(name) { _ -> serializerCreator() }
companion object {
private val logger = contextLogger()
-
- fun isPrimitive(type: Type): Boolean = primitiveTypeName(type) != null
-
- fun primitiveTypeName(type: Type): String? {
- val clazz = type as? Class<*> ?: return null
- return primitiveTypeNames[Primitives.unwrap(clazz)]
- }
-
- fun primitiveType(type: String): Class<*>? {
- return namesOfPrimitiveTypes[type]
- }
-
- private val primitiveTypeNames: Map, String> = mapOf(
- Character::class.java to "char",
- Char::class.java to "char",
- Boolean::class.java to "boolean",
- Byte::class.java to "byte",
- UnsignedByte::class.java to "ubyte",
- Short::class.java to "short",
- UnsignedShort::class.java to "ushort",
- Int::class.java to "int",
- UnsignedInteger::class.java to "uint",
- Long::class.java to "long",
- UnsignedLong::class.java to "ulong",
- Float::class.java to "float",
- Double::class.java to "double",
- Decimal32::class.java to "decimal32",
- Decimal64::class.java to "decimal62",
- Decimal128::class.java to "decimal128",
- Date::class.java to "timestamp",
- UUID::class.java to "uuid",
- ByteArray::class.java to "binary",
- String::class.java to "string",
- Symbol::class.java to "symbol")
-
- private val namesOfPrimitiveTypes: Map> = primitiveTypeNames.map { it.value to it.key }.toMap()
-
- fun nameForType(type: Type): String = when (type) {
- is Class<*> -> {
- primitiveTypeName(type) ?: if (type.isArray) {
- "${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}"
- } else type.name
- }
- is ParameterizedType -> {
- "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
- }
- is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
- is WildcardType -> "?"
- is TypeVariable<*> -> "?"
- else -> throw AMQPNotSerializableException(type, "Unable to render type $type to a string.")
- }
}
- object AnyType : WildcardType {
- override fun getUpperBounds(): Array = arrayOf(Object::class.java)
-
- override fun getLowerBounds(): Array = emptyArray()
-
- override fun toString(): String = "?"
- }
}
\ No newline at end of file
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactoryBuilder.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactoryBuilder.kt
new file mode 100644
index 0000000000..0d05d4e0de
--- /dev/null
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactoryBuilder.kt
@@ -0,0 +1,53 @@
+package net.corda.serialization.internal.amqp
+
+import net.corda.core.DeleteForDJVM
+import net.corda.core.KeepForDJVM
+import net.corda.core.serialization.ClassWhitelist
+import net.corda.serialization.internal.carpenter.ClassCarpenter
+import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
+
+@KeepForDJVM
+object SerializerFactoryBuilder {
+
+ @JvmStatic
+ @JvmOverloads
+ fun build(
+ whitelist: ClassWhitelist,
+ classCarpenter: ClassCarpenter,
+ evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
+ fingerPrinterProvider: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
+ onlyCustomSerializers: Boolean = false): SerializerFactory {
+ return makeFactory(
+ whitelist,
+ classCarpenter,
+ evolutionSerializerProvider,
+ fingerPrinterProvider,
+ onlyCustomSerializers)
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ @DeleteForDJVM
+ fun build(
+ whitelist: ClassWhitelist,
+ carpenterClassLoader: ClassLoader,
+ lenientCarpenterEnabled: Boolean = false,
+ evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider,
+ fingerPrinterProvider: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
+ onlyCustomSerializers: Boolean = false): SerializerFactory {
+ return makeFactory(
+ whitelist,
+ ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenterEnabled),
+ evolutionSerializerProvider,
+ fingerPrinterProvider,
+ onlyCustomSerializers)
+ }
+
+ private fun makeFactory(whitelist: ClassWhitelist,
+ classCarpenter: ClassCarpenter,
+ evolutionSerializerProvider: EvolutionSerializerProvider,
+ fingerPrinterProvider: (SerializerFactory) -> FingerPrinter,
+ onlyCustomSerializers: Boolean) =
+ DefaultSerializerFactory(whitelist, classCarpenter, evolutionSerializerProvider, fingerPrinterProvider,
+ onlyCustomSerializers)
+}
\ No newline at end of file
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/DefaultCacheProvider.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/DefaultCacheProvider.kt
new file mode 100644
index 0000000000..4995c1dbb2
--- /dev/null
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/DefaultCacheProvider.kt
@@ -0,0 +1,11 @@
+package net.corda.serialization.internal.model
+
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * We can't have [ConcurrentHashMap]s in the DJVM, so it must supply its own version of this object which returns
+ * plain old [MutableMap]s instead.
+ */
+object DefaultCacheProvider {
+ fun createCache(): MutableMap = ConcurrentHashMap()
+}
\ No newline at end of file
diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java
index e96763360d..a68e14b572 100644
--- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java
+++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java
@@ -147,10 +147,7 @@ public class JavaPrivatePropertyTests {
//
// Now ensure we actually got a private property serializer
//
- Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
- f.setAccessible(true);
-
- Map, AMQPSerializer>> serializersByDescriptor = uncheckedCast(f.get(factory));
+ Map