mirror of
https://github.com/corda/corda.git
synced 2025-03-15 00:36:49 +00:00
Merge remote-tracking branch 'remotes/open/master' into merges/os-mere-2018-10-31
# Conflicts: # README.md # docs/source/api-persistence.rst # docs/source/node-database.rst
This commit is contained in:
commit
353c96375d
@ -1,4 +1,6 @@
|
||||

|
||||
<p align="center">
|
||||
<img src="https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png" alt="Corda" width="500">
|
||||
</p>
|
||||
|
||||
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=CordaEnterprise_Build&tab=buildTypeStatusDiv"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a>
|
||||
|
||||
|
@ -1,20 +1,23 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import net.corda.client.rpc.internal.RPCClient
|
||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import net.corda.core.context.Actor
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.messaging.ClientRpcSslOptions
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.messaging.ClientRpcSslOptions
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
@ -293,7 +296,7 @@ class CordaRPCClient private constructor(
|
||||
effectiveSerializationEnv
|
||||
} catch (e: IllegalStateException) {
|
||||
try {
|
||||
AMQPClientSerializationScheme.initialiseSerialization(classLoader)
|
||||
AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap())
|
||||
} catch (e: IllegalStateException) {
|
||||
// Race e.g. two of these constructed in parallel, ignore.
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package net.corda.client.rpc.internal.serialization.amqp
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationContext.*
|
||||
import net.corda.core.serialization.SerializationContext.UseCase
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
@ -19,24 +19,25 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
|
||||
* This scheme is for use by the RPC Client calls.
|
||||
*/
|
||||
class AMQPClientSerializationScheme(
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
|
||||
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
|
||||
|
||||
@Suppress("UNUSED")
|
||||
constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })
|
||||
|
||||
companion object {
|
||||
/** Call from main only. */
|
||||
fun initialiseSerialization(classLoader: ClassLoader? = null) {
|
||||
nodeSerializationEnv = createSerializationEnv(classLoader)
|
||||
fun initialiseSerialization(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = AccessOrderLinkedHashMap { 128 }) {
|
||||
nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts)
|
||||
}
|
||||
|
||||
fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
|
||||
fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = AccessOrderLinkedHashMap { 128 }): SerializationEnvironment {
|
||||
return SerializationEnvironment.with(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||
registerScheme(AMQPClientSerializationScheme(emptyList(), serializerFactoriesForContexts))
|
||||
},
|
||||
storageContext = AMQP_STORAGE_CONTEXT,
|
||||
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
|
||||
|
@ -13,170 +13,166 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ContractsDSLTests {
|
||||
class UnwantedCommand : CommandData
|
||||
class UnwantedCommand : CommandData
|
||||
|
||||
interface TestCommands : CommandData {
|
||||
class CommandOne : TypeOnlyCommandData(), TestCommands
|
||||
class CommandTwo : TypeOnlyCommandData(), TestCommands
|
||||
interface TestCommands : CommandData {
|
||||
class CommandOne : TypeOnlyCommandData(), TestCommands
|
||||
class CommandTwo : TypeOnlyCommandData(), TestCommands
|
||||
}
|
||||
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
|
||||
val validCommandOne = CommandWithParties(listOf(megaCorp.publicKey, miniCorp.publicKey), listOf(megaCorp.party, miniCorp.party), TestCommands.CommandOne())
|
||||
val validCommandTwo = CommandWithParties(listOf(megaCorp.publicKey), listOf(megaCorp.party), TestCommands.CommandTwo())
|
||||
val invalidCommand = CommandWithParties(emptyList(), emptyList(), UnwantedCommand())
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class RequireSingleCommandTests(private val testFunction: (Collection<CommandWithParties<CommandData>>) -> CommandWithParties<CommandData>,
|
||||
@Suppress("UNUSED_PARAMETER") description: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand<TestCommands>() }, "Inline version"),
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand(TestCommands::class.java) }, "Interop version")
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
|
||||
val validCommandOne = CommandWithParties(listOf(megaCorp.publicKey, miniCorp.publicKey), listOf(megaCorp.party, miniCorp.party), TestCommands.CommandOne())
|
||||
val validCommandTwo = CommandWithParties(listOf(megaCorp.publicKey), listOf(megaCorp.party), TestCommands.CommandTwo())
|
||||
val invalidCommand = CommandWithParties(emptyList(), emptyList(), UnwantedCommand())
|
||||
@Test
|
||||
fun `check function returns one value`() {
|
||||
val commands = listOf(validCommandOne, invalidCommand)
|
||||
val returnedCommand = testFunction(commands)
|
||||
assertEquals(returnedCommand, validCommandOne, "they should be the same")
|
||||
}
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class RequireSingleCommandTests(private val testFunction: (Collection<CommandWithParties<CommandData>>) -> CommandWithParties<CommandData>,
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `check error is thrown if more than one valid command`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
testFunction(commands)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check error is thrown when command is of wrong type`() {
|
||||
val commands = listOf(invalidCommand)
|
||||
Assertions.assertThatThrownBy { testFunction(commands) }
|
||||
.isInstanceOf(IllegalStateException::class.java)
|
||||
.hasMessage("Required net.corda.core.contracts.TestCommands command")
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class SelectWithSingleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, PublicKey?, AbstractParty?) -> Iterable<CommandWithParties<CommandData>>,
|
||||
@Suppress("UNUSED_PARAMETER") description: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select<TestCommands>(signer, party) }, "Inline version"),
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select(TestCommands::class.java, signer, party) }, "Interop version")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns all values`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
testFunction(commands, null, null)
|
||||
assertEquals(2, commands.size)
|
||||
assertTrue(commands.contains(validCommandOne))
|
||||
assertTrue(commands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function does not return invalid command types`() {
|
||||
val commands = listOf(validCommandOne, invalidCommand)
|
||||
val filteredCommands = testFunction(commands, null, null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(invalidCommand))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid signers`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, miniCorp.publicKey, null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid parties`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, null, miniCorp.party).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class SelectWithMultipleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, Collection<PublicKey>?, Collection<Party>?) -> Iterable<CommandWithParties<CommandData>>,
|
||||
@Suppress("UNUSED_PARAMETER") description: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand<TestCommands>() }, "Inline version"),
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand(TestCommands::class.java) }, "Interop version")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check function returns one value`() {
|
||||
val commands = listOf(validCommandOne, invalidCommand)
|
||||
val returnedCommand = testFunction(commands)
|
||||
assertEquals(returnedCommand, validCommandOne, "they should be the same")
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `check error is thrown if more than one valid command`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
testFunction(commands)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check error is thrown when command is of wrong type`() {
|
||||
val commands = listOf(invalidCommand)
|
||||
Assertions.assertThatThrownBy { testFunction(commands) }
|
||||
.isInstanceOf(IllegalStateException::class.java)
|
||||
.hasMessage("Required net.corda.core.contracts.ContractsDSLTests.TestCommands command")
|
||||
}
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select<TestCommands>(signers, party) }, "Inline version"),
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select(TestCommands::class.java, signers, party) }, "Interop version")
|
||||
)
|
||||
}
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class SelectWithSingleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, PublicKey?, AbstractParty?) -> Iterable<CommandWithParties<CommandData>>,
|
||||
@Suppress("UNUSED_PARAMETER") description: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select<TestCommands>(signer, party) }, "Inline version"),
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select(TestCommands::class.java, signer, party) }, "Interop version")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns all values`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
testFunction(commands, null, null)
|
||||
assertEquals(2, commands.size)
|
||||
assertTrue(commands.contains(validCommandOne))
|
||||
assertTrue(commands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function does not return invalid command types`() {
|
||||
val commands = listOf(validCommandOne, invalidCommand)
|
||||
val filteredCommands = testFunction(commands, null, null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(invalidCommand))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid signers`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, miniCorp.publicKey, null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid parties`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, null, miniCorp.party).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
@Test
|
||||
fun `check that function returns all values`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
testFunction(commands, null, null)
|
||||
assertEquals(2, commands.size)
|
||||
assertTrue(commands.contains(validCommandOne))
|
||||
assertTrue(commands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class SelectWithMultipleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, Collection<PublicKey>?, Collection<Party>?) -> Iterable<CommandWithParties<CommandData>>,
|
||||
@Suppress("UNUSED_PARAMETER") description: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select<TestCommands>(signers, party) }, "Inline version"),
|
||||
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select(TestCommands::class.java, signers, party) }, "Interop version")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns all values`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
testFunction(commands, null, null)
|
||||
assertEquals(2, commands.size)
|
||||
assertTrue(commands.contains(validCommandOne))
|
||||
assertTrue(commands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function does not return invalid command types`() {
|
||||
val commands = listOf(validCommandOne, invalidCommand)
|
||||
val filteredCommands = testFunction(commands, null, null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(invalidCommand))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid signers`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, listOf(megaCorp.publicKey), null).toList()
|
||||
assertEquals(2, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertTrue(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from all valid signers`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, listOf(miniCorp.publicKey, megaCorp.publicKey), null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid parties`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, null, listOf(megaCorp.party)).toList()
|
||||
assertEquals(2, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertTrue(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from all valid parties`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, null, listOf(miniCorp.party, megaCorp.party)).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
@Test
|
||||
fun `check that function does not return invalid command types`() {
|
||||
val commands = listOf(validCommandOne, invalidCommand)
|
||||
val filteredCommands = testFunction(commands, null, null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(invalidCommand))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid signers`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, listOf(megaCorp.publicKey), null).toList()
|
||||
assertEquals(2, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertTrue(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from all valid signers`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, listOf(miniCorp.publicKey, megaCorp.publicKey), null).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from valid parties`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, null, listOf(megaCorp.party)).toList()
|
||||
assertEquals(2, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertTrue(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check that function returns commands from all valid parties`() {
|
||||
val commands = listOf(validCommandOne, validCommandTwo)
|
||||
val filteredCommands = testFunction(commands, null, listOf(miniCorp.party, megaCorp.party)).toList()
|
||||
assertEquals(1, filteredCommands.size)
|
||||
assertTrue(filteredCommands.contains(validCommandOne))
|
||||
assertFalse(filteredCommands.contains(validCommandTwo))
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,8 @@ time effectively stop being a part of the network.
|
||||
**Signature constraints.** These are not yet supported, but once implemented they will allow a state to require a JAR
|
||||
signed by a specified identity, via the regular Java ``jarsigner`` tool. This will be the most flexible type
|
||||
and the smoothest to deploy: no restarts or contract upgrade transactions are needed.
|
||||
When CorDapp is build using :ref:`corda-gradle-plugin <cordapp_build_system_signing_cordapp_jar_ref>` the JAR is signed
|
||||
by Corda development key by default, an external keystore can be configured or signing can be disabled.
|
||||
|
||||
**Defaults.** Currently, the default constraint type is either a zone constraint, if the network parameters in effect when the
|
||||
transaction is built contain an entry for that contract class, or a hash constraint if not. Once the Signature Constraints are introduced,
|
||||
|
@ -113,7 +113,7 @@ Several examples of entities and mappings are provided in the codebase, includin
|
||||
:language: kotlin
|
||||
|
||||
.. note:: Ensure table and column names are compatible with the naming convention of database vendors for which the Cordapp will be deployed,
|
||||
e.g. prior to Oracle 12.2 the maximum length of table/column name is 30 bytes (the exact number of characters depends on the database encoding).
|
||||
e.g. for Oracle database, prior to version 12.2 the maximum length of table/column name is 30 bytes (the exact number of characters depends on the database encoding).
|
||||
|
||||
Identity mapping
|
||||
----------------
|
||||
|
@ -11,20 +11,23 @@ Interacting with a node
|
||||
|
||||
Overview
|
||||
--------
|
||||
You should interact with your node using the `CordaRPCClient`_ library. This library that allows you to easily
|
||||
write clients in a JVM-compatible language to interact with a running node. The library connects to the node using a
|
||||
message queue protocol and then provides a simple RPC interface to interact with the node. You make calls on a JVM
|
||||
object as normal, and the marshalling back and forth is handled for you.
|
||||
To interact with your node, you need to write a client in a JVM-compatible language using the `CordaRPCClient`_ class.
|
||||
This class allows you to connect to your node via a message queue protocol and provides a simple RPC interface for
|
||||
interacting with the node. You make calls on a JVM object as normal, and the marshalling back-and-forth is handled for
|
||||
you.
|
||||
|
||||
.. warning:: The built-in Corda webserver is deprecated and unsuitable for production use. If you want to interact with
|
||||
your node via HTTP, you will need to stand up your own webserver, then create an RPC connection between your node
|
||||
and this webserver using the `CordaRPCClient`_ library. You can find an example of how to do this using the popular
|
||||
Spring Boot server `here <https://github.com/corda/spring-webserver>`_.
|
||||
your node via HTTP, you will need to stand up your own webserver that connects to your node using the
|
||||
`CordaRPCClient`_ class. You can find an example of how to do this using the popular Spring Boot server
|
||||
`here <https://github.com/corda/spring-webserver>`_.
|
||||
|
||||
Connecting to a node via RPC
|
||||
----------------------------
|
||||
`CordaRPCClient`_ provides a ``start`` method that takes the node's RPC address and returns a `CordaRPCConnection`_.
|
||||
`CordaRPCConnection`_ provides a ``proxy`` method that takes an RPC username and password and returns a `CordaRPCOps`_
|
||||
To use `CordaRPCClient`_, you must add ``net.corda:corda-rpc:$corda_release_version`` as a ``cordaCompile`` dependency
|
||||
in your client's ``build.gradle`` file.
|
||||
|
||||
`CordaRPCClient`_ has a ``start`` method that takes the node's RPC address and returns a `CordaRPCConnection`_.
|
||||
`CordaRPCConnection`_ has a ``proxy`` method that takes an RPC username and password and returns a `CordaRPCOps`_
|
||||
object that you can use to interact with the node.
|
||||
|
||||
Here is an example of using `CordaRPCClient`_ to connect to a node and log the current time on its internal clock:
|
||||
|
@ -109,6 +109,108 @@ in Gradle. See the example below, specifically the ``apache-commons`` include.
|
||||
For further information about managing dependencies, see
|
||||
`the Gradle docs <https://docs.gradle.org/current/userguide/dependency_management.html>`_.
|
||||
|
||||
.. _cordapp_build_system_signing_cordapp_jar_ref:
|
||||
|
||||
Signing the CorDapp JAR
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
The ``cordapp`` plugin can sign the generated CorDapp JAR file using `JAR signing and verification tool <https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html>`_.
|
||||
Signing the CorDapp enables its contract classes to use signature constraints instead of other types of the constraints,
|
||||
for constraints explanation refer to :doc:`api-contract-constraints`.
|
||||
By default the JAR file is signed by Corda development certificate.
|
||||
The signing process can be disabled or configured to use an external keystore.
|
||||
The ``signing`` entry may contain the following parameters:
|
||||
|
||||
* ``enabled`` the control flag to enable signing process, by default is set to ``true``, set to ``false`` to disable signing
|
||||
* ``options`` any relevant parameters of `SignJar ANT task <https://ant.apache.org/manual/Tasks/signjar.html>`_,
|
||||
by default the JAR file is signed with Corda development key, the external keystore can be specified,
|
||||
the minimal list of required options is shown below, for other options referer to `SignJar task <https://ant.apache.org/manual/Tasks/signjar.html>`_:
|
||||
|
||||
* ``keystore`` the path to the keystore file, by default *cordadevcakeys.jks* keystore is shipped with the plugin
|
||||
* ``alias`` the alias to sign under, the default value is *cordaintermediateca*
|
||||
* ``storepass`` the keystore password, the default value is *cordacadevpass*
|
||||
* ``keypass`` the private key password if it's different than the password for the keystore, the default value is *cordacadevkeypass*
|
||||
* ``storetype`` the keystore type, the default value is *JKS*
|
||||
|
||||
The parameters can be also set by system properties passed to Gradle build process.
|
||||
The system properties should be named as the relevant option name prefixed with '*signing.*', e.g.
|
||||
a value for ``alias`` can be taken from the ``signing.alias`` system property. The following system properties can be used:
|
||||
``signing.enabled``, ``signing.keystore``, ``signing.alias``, ``signing.storepass``, ``signing.keypass``, ``signing.storetype``.
|
||||
The resolution order of a configuration value is as follows: the signing process takes a value specified in the ``signing`` entry first,
|
||||
the empty string *""* is also considered as the correct value.
|
||||
If the option is not set, the relevant system property named *signing.option* is tried.
|
||||
If the system property is not set then the value defaults to the configuration of the Corda development certificate.
|
||||
|
||||
The example ``cordapp`` plugin with plugin ``signing`` configuration:
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
cordapp {
|
||||
signing {
|
||||
enabled true
|
||||
options {
|
||||
keystore "/path/to/jarSignKeystore.p12"
|
||||
alias "cordapp-signer"
|
||||
storepass "secret1!"
|
||||
keypass "secret1!"
|
||||
storetype "PKCS12"
|
||||
}
|
||||
}
|
||||
//...
|
||||
|
||||
CorDapp auto-signing allows to use signature constraints for contracts from the CorDapp
|
||||
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.
|
||||
|
||||
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)
|
||||
and for a production build (using an external keystore).
|
||||
The example system properties setup for the build process which overrides signing options:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
./gradlew -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"
|
||||
|
||||
Without providing the system properties, the build will sign the CorDapp with the default Corda development keystore:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
./gradlew
|
||||
|
||||
CorDapp signing can be disabled for a build:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
./gradlew -Dsigning.enabled=false
|
||||
|
||||
Other system properties can be explicitly assigned to options by calling ``System.getProperty`` in ``cordapp`` plugin configuration.
|
||||
For example the below configuration sets the specific signing algorithm when a system property is available otherwise defaults to an empty string:
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
cordapp {
|
||||
signing {
|
||||
options {
|
||||
sigalg System.getProperty('custom.sigalg','')
|
||||
}
|
||||
}
|
||||
//...
|
||||
|
||||
Then the build process can set the value for *custom.sigalg* system property and other system properties recognized by ``cordapp`` plugin:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
./gradlew -Dcustom.sigalg="SHA256withECDSA" -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"
|
||||
|
||||
To check if CorDapp is signed use `JAR signing and verification tool <https://docs.oracle.com/javase/tutorial/deployment/jar/verify.html>`_:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
jarsigner --verify path/to/cordapp.jar
|
||||
|
||||
Cordformation plugin can also sign CorDapps JARs, when deploying set of nodes, see :doc:`generating-a-node`.
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
Below is a sample of what a CorDapp's Gradle dependencies block might look like. When building your own CorDapp, you should
|
||||
|
@ -143,6 +143,66 @@ To copy the same file to all nodes `ext.drivers` can be defined in the top level
|
||||
}
|
||||
}
|
||||
|
||||
Signing Cordapp JARs
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
Cordform entry ``signing`` configures the signing of CorDapp JARs.
|
||||
Signing the CorDapp enables its contract classes to use signature constraints instead of other types of the constraints :doc:`api-contract-constraints`.
|
||||
By default all CorDapp JARs are signed by Corda development certificate.
|
||||
The sign task may use an external keystore, or create a new one.
|
||||
The ``signing`` entry may contain the following parameters:
|
||||
|
||||
* ``enabled`` the control flag to enable signing process, by default is set to ``true``, set to ``false`` to disable signing
|
||||
* ``all`` if set to ``true`` (by default) all CorDapps inside *cordapp* subdirectory will be signed, otherwise if ``false`` then only the generated Cordapp will be signed
|
||||
* ``options`` any relevant parameters of `SignJar ANT task <https://ant.apache.org/manual/Tasks/signjar.html>`_ and `GenKey ANT task <https://ant.apache.org/manual/Tasks/genkey.html>`_,
|
||||
by default the JAR file is signed by Corda development key, the external keystore can be specified,
|
||||
the minimal list of required options is shown below, for other options referer to `SignJar task <https://ant.apache.org/manual/Tasks/signjar.html>`_:
|
||||
|
||||
* ``keystore`` the path to the keystore file, by default *cordadevcakeys.jks* keystore is shipped with the plugin
|
||||
* ``alias`` the alias to sign under, the default value is *cordaintermediateca*
|
||||
* ``storepass`` the keystore password, the default value is *cordacadevpass*
|
||||
* ``keypass`` the private key password if it's different than the password for the keystore, the default value is *cordacadevkeypass*
|
||||
* ``storetype`` the keystore type, the default value is *JKS*
|
||||
* ``dname`` the distinguished name for entity, the option is used when ``generateKeystore true`` only
|
||||
* ``keyalg`` the method to use when generating name-value pair, the value defaults to *RSA* as Corda doesn't support *DSA*, the option is used when ``generateKeystore true`` only
|
||||
|
||||
* ``generateKeystore`` the flag to generate a keystore, it is set to ``false`` by default. If set to ``true`` then ad hock keystore is created and its key isused
|
||||
instead of the default Corda development key or any external key.
|
||||
The same ``options`` to specify an external keystore are used to define the newly created keystore. Additionally
|
||||
``dname`` and ``keyalg`` are required. Other options are described in `GenKey task <https://ant.apache.org/manual/Tasks/genkey.html>`_.
|
||||
If the existing keystore is already present the task will reuse it, however if the file is inside the *build* directory,
|
||||
then it will be deleted when Gradle *clean* task is run.
|
||||
|
||||
The example below shows the minimal set of ``options`` needed to create a dummy keystore:
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
signing {
|
||||
enabled true
|
||||
generateKeystore true
|
||||
all false
|
||||
options {
|
||||
keystore "./build/nodes/jarSignKeystore.p12"
|
||||
alias "cordapp-signer"
|
||||
storepass "secret1!"
|
||||
storetype "PKCS12"
|
||||
dname "OU=Dummy Cordapp Distributor, O=Corda, L=London, C=GB"
|
||||
keyalg "RSA"
|
||||
}
|
||||
}
|
||||
//...
|
||||
|
||||
Contracts classes from signed CorDapp JARs will be checked by signature constraints by default.
|
||||
You can force them to be checked by zone constraints by adding contract class names to ``includeWhitelist`` entry,
|
||||
the list will generate *include_whitelist.txt* file used internally by :doc:`network-bootstrapper` tool.
|
||||
Refer to :doc:`api-contract-constraints` to understand implication of different constraint types before adding ``includeWhitelist`` to ``deployNodes`` task.
|
||||
The snippet below configures contracts classes from Finance CorDapp to be verified using zone constraints instead of signature constraints:
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
includeWhitelist = [ "net.corda.finance.contracts.asset.Cash", "net.corda.finance.contracts.asset.CommercialPaper" ]
|
||||
//...
|
||||
|
||||
Specifying a custom webserver
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -324,7 +324,7 @@ Example node configuration for PostgreSQL:
|
||||
|
||||
dataSourceProperties = {
|
||||
dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
|
||||
dataSource.url = "jdbc:postgresql://[HOST]:[PORT]/postgres"
|
||||
dataSource.url = "jdbc:postgresql://[HOST]:[PORT]/[DATABASE]"
|
||||
dataSource.user = [USER]
|
||||
dataSource.password = [PASSWORD]
|
||||
}
|
||||
@ -353,6 +353,60 @@ To delete existing data from the database, drop the existing schema and recreate
|
||||
|
||||
DROP SCHEMA IF EXISTS "[SCHEMA]" CASCADE;
|
||||
|
||||
Node database tables
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, the node database has the following tables:
|
||||
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Table name | Columns |
|
||||
+=============================+==========================================================================================================================================================================================================+
|
||||
| DATABASECHANGELOG | ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, EXECTYPE, MD5SUM, DESCRIPTION, COMMENTS, TAG, LIQUIBASE, CONTEXTS, LABELS, DEPLOYMENT_ID |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| DATABASECHANGELOGLOCK | ID, LOCKED, LOCKGRANTED, LOCKEDBY |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_ATTACHMENTS | ATT_ID, CONTENT, FILENAME, INSERTION_DATE, UPLOADER |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_ATTACHMENTS_CONTRACTS | ATT_ID, CONTRACT_CLASS_NAME |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_CHECKPOINTS | CHECKPOINT_ID, CHECKPOINT_VALUE |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_CONTRACT_UPGRADES | STATE_REF, CONTRACT_CLASS_NAME |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_IDENTITIES | PK_HASH, IDENTITY_VALUE |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_INFOS | NODE_INFO_ID, NODE_INFO_HASH, PLATFORM_VERSION, SERIAL |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_INFO_HOSTS | HOST_NAME, PORT, NODE_INFO_ID, HOSTS_ID |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_INFO_PARTY_CERT | PARTY_NAME, ISMAIN, OWNING_KEY_HASH, PARTY_CERT_BINARY |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_LINK_NODEINFO_PARTY | NODE_INFO_ID, PARTY_NAME |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_MESSAGE_IDS | MESSAGE_ID, INSERTION_TIME, SENDER, SEQUENCE_NUMBER |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_NAMES_IDENTITIES | NAME, PK_HASH |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_OUR_KEY_PAIRS | PUBLIC_KEY_HASH, PRIVATE_KEY, PUBLIC_KEY |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_PROPERTIES | PROPERTY_KEY, PROPERTY_VALUE |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_SCHEDULED_STATES | OUTPUT_INDEXTRANSACTION_IDSCHEDULED_AT |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| NODE_TRANSACTIONS | TX_ID, TRANSACTION_VALUE, STATE_MACHINE_RUN_ID |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| VAULT_FUNGIBLE_STATES | OUTPUT_INDEX, TRANSACTION_ID, ISSUER_NAME, ISSUER_REF, OWNER_NAME, QUANTITY |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| VAULT_FUNGIBLE_STATES_PARTS | OUTPUT_INDEX, TRANSACTION_ID, PARTICIPANTS |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| VAULT_LINEAR_STATES | OUTPUT_INDEX, TRANSACTION_ID, EXTERNAL_ID, UUID |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| VAULT_LINEAR_STATES_PARTS | OUTPUT_INDEX, TRANSACTION_ID, PARTICIPANTS |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| VAULT_STATES | OUTPUT_INDEX, TRANSACTION_ID, CONSUMED_TIMESTAMP, CONTRACT_STATE_CLASS_NAME, LOCK_ID, LOCK_TIMESTAMP, NOTARY_NAME, RECORDED_TIMESTAMP, STATE_STATUS, RELEVANCY_STATUS, CONSTRAINT_TYPE, CONSTRAINT_DATA |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| VAULT_TRANSACTION_NOTES | SEQ_NO, NOTE, TRANSACTION_ID |
|
||||
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
Guideline for adding support for other databases
|
||||
````````````````````````````````````````````````
|
||||
|
@ -39,22 +39,23 @@ weakly or untyped string-based serialisation schemes like JSON or XML. The prima
|
||||
Whitelisting
|
||||
------------
|
||||
|
||||
In classic Java serialization, any class on the JVM classpath can be deserialized. This has shown to be a source of exploits
|
||||
and vulnerabilities by exploiting the large set of 3rd party libraries on the classpath as part of the dependencies of
|
||||
a JVM application and a carefully crafted stream of bytes to be deserialized. In Corda, we prevent just any class from
|
||||
being deserialized (and pro-actively during serialization) by insisting that each object's class belongs on a whitelist
|
||||
of allowed classes.
|
||||
In classic Java serialization, any class on the JVM classpath can be deserialized. This is a source of exploits and
|
||||
vulnerabilities that exploit the large set of third-party libraries that are added to the classpath as part of a JVM
|
||||
application's dependencies and carefully craft a malicious stream of bytes to be deserialized. In Corda, we strictly
|
||||
control which classes can be deserialized (and, pro-actively, serialized) by insisting that each (de)serializable class
|
||||
is part of a whitelist of allowed classes.
|
||||
|
||||
Classes get onto the whitelist via one of three mechanisms:
|
||||
To add a class to the whitelist, you must use either of the following mechanisms:
|
||||
|
||||
#. Via the ``@CordaSerializable`` annotation. In order to whitelist a class, this annotation can be present on the
|
||||
class itself, on any of the super classes or on any interface implemented by the class or super classes or any
|
||||
interface extended by an interface implemented by the class or superclasses.
|
||||
#. By implementing the ``SerializationWhitelist`` interface and specifying a list of `whitelist` classes.
|
||||
#. Via the built in Corda whitelist (see the class ``DefaultWhitelist``). Whilst this is not user editable, it does list
|
||||
common JDK classes that have been whitelisted for your convenience.
|
||||
#. Add the ``@CordaSerializable`` annotation to the class. This annotation can be present on the
|
||||
class itself, on any super class of the class, on any interface implemented by the class or its super classes, or any
|
||||
interface extended by an interface implemented by the class or its super classes.
|
||||
#. Implement the ``SerializationWhitelist`` interface and specify a list of whitelisted classes.
|
||||
|
||||
The annotation is the preferred method for whitelisting. An example is shown in :doc:`tutorial-clientrpc-api`.
|
||||
There is also a built-in Corda whitelist (see the ``DefaultWhitelist`` class) that whitelists common JDK classes for
|
||||
convenience. This whitelist is not user-editable.
|
||||
|
||||
The annotation is the preferred method for whitelisting. An example is shown in :doc:`tutorial-clientrpc-api`.
|
||||
It's reproduced here as an example of both ways you can do this for a couple of example classes.
|
||||
|
||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/ClientRpcTutorial.kt
|
||||
|
@ -90,6 +90,8 @@ cordapp {
|
||||
targetPlatformVersion corda_platform_version.toInteger()
|
||||
minimumPlatformVersion 1
|
||||
}
|
||||
// By default the Cordapp is signed by Corda development certificate, for production build pass the following system properties to Gradle to use specific keystore e.g:
|
||||
// ./gradlew -Dsigning.enabled="true" -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"
|
||||
}
|
||||
|
||||
publish {
|
||||
|
@ -3,6 +3,7 @@ package net.corda.node.internal
|
||||
import com.codahale.metrics.JmxReporter
|
||||
import com.codahale.metrics.MetricFilter
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
|
||||
import com.palominolabs.metrics.newrelic.NewRelicReporter
|
||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
@ -22,6 +23,7 @@ import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -56,6 +58,7 @@ import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
||||
import net.corda.serialization.internal.*
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import org.apache.commons.lang.SystemUtils
|
||||
import org.h2.jdbc.JdbcSQLException
|
||||
import org.slf4j.Logger
|
||||
@ -502,8 +505,8 @@ open class Node(configuration: NodeConfiguration,
|
||||
val classloader = cordappLoader.appClassLoader
|
||||
nodeSerializationEnv = SerializationEnvironment.with(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
|
||||
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps))
|
||||
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap()))
|
||||
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap()))
|
||||
},
|
||||
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
||||
rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader),
|
||||
|
@ -9,7 +9,6 @@ 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.custom.RxNotificationSerializer
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* When set as the serialization scheme, defines the RPC Server serialization scheme as using the Corda
|
||||
@ -17,9 +16,10 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
*/
|
||||
class AMQPServerSerializationScheme(
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
|
||||
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
|
||||
|
||||
constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })
|
||||
|
||||
|
@ -2,6 +2,7 @@ package net.corda.node.services.persistence
|
||||
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
|
||||
import java.io.InputStream
|
||||
|
||||
interface AttachmentStorageInternal : AttachmentStorage {
|
||||
@ -10,4 +11,9 @@ interface AttachmentStorageInternal : AttachmentStorage {
|
||||
* and is only for the node.
|
||||
*/
|
||||
fun privilegedImportAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId
|
||||
|
||||
/**
|
||||
* Similar to above but returns existing [AttachmentId] instead of throwing [DuplicateAttachmentException]
|
||||
*/
|
||||
fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId
|
||||
}
|
||||
|
@ -6,13 +6,11 @@ import com.google.common.hash.HashCode
|
||||
import com.google.common.hash.Hashing
|
||||
import com.google.common.hash.HashingInputStream
|
||||
import com.google.common.io.CountingInputStream
|
||||
import net.corda.core.ClientRelevantError
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
@ -286,6 +284,14 @@ class NodeAttachmentService(
|
||||
return import(jar, uploader, filename)
|
||||
}
|
||||
|
||||
override fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
|
||||
return try {
|
||||
import(jar, uploader, filename)
|
||||
} catch (faee: java.nio.file.FileAlreadyExistsException) {
|
||||
AttachmentId.parse(faee.message!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction {
|
||||
currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
|
||||
}
|
||||
@ -306,9 +312,7 @@ class NodeAttachmentService(
|
||||
val id = bytes.sha256()
|
||||
if (!hasAttachment(id)) {
|
||||
checkIsAValidJAR(bytes.inputStream())
|
||||
|
||||
val jarSigners = getSigners(bytes)
|
||||
|
||||
val session = currentDBSession()
|
||||
val attachment = NodeAttachmentService.DBAttachment(
|
||||
attId = id.toString(),
|
||||
@ -318,14 +322,24 @@ class NodeAttachmentService(
|
||||
contractClassNames = contractClassNames,
|
||||
signers = jarSigners
|
||||
)
|
||||
|
||||
session.save(attachment)
|
||||
attachmentCount.inc()
|
||||
log.info("Stored new attachment $id")
|
||||
id
|
||||
} else {
|
||||
throw DuplicateAttachmentException(id.toString())
|
||||
return@withContractsInJar id
|
||||
}
|
||||
if (isUploaderTrusted(uploader)) {
|
||||
val session = currentDBSession()
|
||||
val attachment = session.get(NodeAttachmentService.DBAttachment::class.java, id.toString())
|
||||
// update the `upLoader` field (as the existing attachment may have been resolved from a peer)
|
||||
if (attachment.uploader != uploader) {
|
||||
attachment.uploader = uploader
|
||||
session.saveOrUpdate(attachment)
|
||||
log.info("Updated attachment $id with uploader $uploader")
|
||||
attachmentCache.invalidate(id)
|
||||
attachmentContentCache.invalidate(id)
|
||||
}
|
||||
}
|
||||
throw DuplicateAttachmentException(id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.JarSignatureTestUtils.createJar
|
||||
import net.corda.core.JarSignatureTestUtils.generateKey
|
||||
import net.corda.core.JarSignatureTestUtils.signJar
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -48,6 +49,7 @@ import javax.tools.StandardLocation
|
||||
import javax.tools.ToolProvider
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
|
||||
@ -124,6 +126,30 @@ class NodeAttachmentServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() {
|
||||
val contractJarName = makeTestContractJar("com.example.MyContract")
|
||||
val testJar = dir.resolve(contractJarName)
|
||||
val expectedHash = testJar.readAll().sha256()
|
||||
|
||||
// PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER)
|
||||
// TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER)
|
||||
|
||||
database.transaction {
|
||||
val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) }
|
||||
assertEquals(expectedHash, id)
|
||||
val attachment1 = storage.openAttachment(expectedHash)
|
||||
|
||||
val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) }
|
||||
assertEquals(expectedHash, id2)
|
||||
val attachment2 = storage.openAttachment(expectedHash)
|
||||
|
||||
assertNotEquals(attachment1, attachment2)
|
||||
assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader)
|
||||
assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing is not cached`() {
|
||||
val (testJar, expectedHash) = makeTestJar()
|
||||
|
@ -1,13 +1,12 @@
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import java.lang.ClassLoader
|
||||
|
||||
/**
|
||||
* Drop-in replacement for [AttachmentsClassLoaderBuilder] in the serialization module.
|
||||
* This version is not strongly-coupled to [net.corda.core.node.ServiceHub].
|
||||
*/
|
||||
@Suppress("UNUSED", "UNUSED_PARAMETER")
|
||||
internal class AttachmentsClassLoaderBuilder(private val properties: Map<Any, Any>, private val deserializationClassLoader: ClassLoader) {
|
||||
fun build(attachmentHashes: List<SecureHash>): AttachmentsClassLoader? = null
|
||||
internal class AttachmentsClassLoaderBuilder() {
|
||||
fun build(attachmentHashes: List<SecureHash>, properties: Map<Any, Any>, deserializationClassLoader: ClassLoader): AttachmentsClassLoader? = null
|
||||
}
|
@ -2,8 +2,11 @@ package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.EncodingWhitelist
|
||||
import net.corda.core.serialization.SerializationEncoding
|
||||
import net.corda.core.serialization.internal.CheckpointSerializationContext
|
||||
import java.lang.UnsupportedOperationException
|
||||
|
||||
@KeepForDJVM
|
||||
data class CheckpointSerializationContextImpl @JvmOverloads constructor(
|
||||
@ -13,17 +16,13 @@ data class CheckpointSerializationContextImpl @JvmOverloads constructor(
|
||||
override val objectReferencesEnabled: Boolean,
|
||||
override val encoding: SerializationEncoding?,
|
||||
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : CheckpointSerializationContext {
|
||||
private val builder = AttachmentsClassLoaderBuilder(properties, deserializationClassLoader)
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context.
|
||||
* Unsupported for checkpoints.
|
||||
*/
|
||||
override fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): CheckpointSerializationContext {
|
||||
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this
|
||||
val classLoader = builder.build(attachmentHashes) ?: return this
|
||||
return withClassLoader(classLoader)
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun withProperty(property: Any, value: Any): CheckpointSerializationContext {
|
||||
|
@ -31,8 +31,10 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
||||
override val useCase: SerializationContext.UseCase,
|
||||
override val encoding: SerializationEncoding?,
|
||||
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
|
||||
override val lenientCarpenterEnabled: Boolean = false) : SerializationContext {
|
||||
private val builder = AttachmentsClassLoaderBuilder(properties, deserializationClassLoader)
|
||||
override val lenientCarpenterEnabled: Boolean = false,
|
||||
private val builder: AttachmentsClassLoaderBuilder = AttachmentsClassLoaderBuilder()
|
||||
) : SerializationContext {
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
@ -41,7 +43,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
||||
*/
|
||||
override fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): SerializationContext {
|
||||
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this
|
||||
val classLoader = builder.build(attachmentHashes) ?: return this
|
||||
val classLoader = builder.build(attachmentHashes, properties, deserializationClassLoader) ?: return this
|
||||
return withClassLoader(classLoader)
|
||||
}
|
||||
|
||||
@ -75,13 +77,13 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
||||
* can replace it with an alternative version.
|
||||
*/
|
||||
@DeleteForDJVM
|
||||
internal class AttachmentsClassLoaderBuilder(private val properties: Map<Any, Any>, private val deserializationClassLoader: ClassLoader) {
|
||||
private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build()
|
||||
class AttachmentsClassLoaderBuilder() {
|
||||
private val cache: Cache<Pair<List<SecureHash>, ClassLoader>, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build()
|
||||
|
||||
fun build(attachmentHashes: List<SecureHash>): AttachmentsClassLoader? {
|
||||
fun build(attachmentHashes: List<SecureHash>, properties: Map<Any, Any>, deserializationClassLoader: ClassLoader): AttachmentsClassLoader? {
|
||||
val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContext ?: return null // Some tests don't set one.
|
||||
try {
|
||||
return cache.get(attachmentHashes) {
|
||||
return cache.get(Pair(attachmentHashes, deserializationClassLoader)) {
|
||||
val missing = ArrayList<SecureHash>()
|
||||
val attachments = ArrayList<Attachment>()
|
||||
attachmentHashes.forEach { id ->
|
||||
@ -120,12 +122,13 @@ open class SerializationFactoryImpl(
|
||||
// truncate sequence to at most magicSize, and make sure it's a copy to avoid holding onto large ByteArrays
|
||||
val magic = CordaSerializationMagic(byteSequence.slice(end = magicSize).copyBytes())
|
||||
val lookupKey = magic to target
|
||||
return schemes.computeIfAbsent(lookupKey) {
|
||||
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
|
||||
return (schemes[lookupKey] ?: schemes.computeIfAbsent(lookupKey) {
|
||||
registeredSchemes.filter { it.canDeserializeVersion(magic, target) }.forEach { return@computeIfAbsent it } // XXX: Not single?
|
||||
logger.warn("Cannot find serialization scheme for: [$lookupKey, " +
|
||||
"${if (magic == amqpMagic) "AMQP" else "UNKNOWN MAGIC"}] registeredSchemes are: $registeredSchemes")
|
||||
throw UnsupportedOperationException("Serialization scheme $lookupKey not supported.")
|
||||
} to magic
|
||||
}) to magic
|
||||
}
|
||||
|
||||
@Throws(NotSerializableException::class)
|
||||
|
@ -40,12 +40,19 @@ interface SerializerFactoryFactory {
|
||||
@KeepForDJVM
|
||||
abstract class AbstractAMQPSerializationScheme(
|
||||
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
private val serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
|
||||
maybeNotConcurrentSerializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
|
||||
val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
|
||||
) : SerializationScheme {
|
||||
@DeleteForDJVM
|
||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap(128))
|
||||
|
||||
// This is a bit gross but a broader check for ConcurrentMap is not allowed inside DJVM.
|
||||
private val serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) {
|
||||
Collections.synchronizedMap(maybeNotConcurrentSerializerFactoriesForContexts)
|
||||
} else {
|
||||
maybeNotConcurrentSerializerFactoriesForContexts
|
||||
}
|
||||
|
||||
// TODO: This method of initialisation for the Whitelist and plugin serializers will have to change
|
||||
// when we have per-cordapp contexts and dynamic app reloading but for now it's the easiest way
|
||||
companion object {
|
||||
@ -166,8 +173,9 @@ abstract class AbstractAMQPSerializationScheme(
|
||||
open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer
|
||||
|
||||
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
return synchronized(serializerFactoriesForContexts) {
|
||||
serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
|
||||
val key = Pair(context.whitelist, context.deserializationClassLoader)
|
||||
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
|
||||
return serializerFactoriesForContexts[key] ?: serializerFactoriesForContexts.computeIfAbsent(key) {
|
||||
when (context.useCase) {
|
||||
SerializationContext.UseCase.RPCClient ->
|
||||
rpcClientSerializerFactory(context)
|
||||
@ -178,7 +186,6 @@ abstract class AbstractAMQPSerializationScheme(
|
||||
registerCustomSerializers(context, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
|
||||
|
@ -7,7 +7,10 @@ import net.corda.core.StubOutForDJVM
|
||||
import net.corda.core.internal.kotlinObjectInstance
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.contextLogger
|
||||
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 org.apache.qpid.proton.amqp.*
|
||||
import java.io.NotSerializableException
|
||||
@ -95,6 +98,9 @@ open class SerializerFactory(
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Look up, and manufacture if necessary, a serializer for the given type.
|
||||
*
|
||||
@ -106,50 +112,56 @@ open class SerializerFactory(
|
||||
// can be useful to enable but will be *extremely* chatty if you do
|
||||
logger.trace { "Get Serializer for $actualClass ${declaredType.typeName}" }
|
||||
|
||||
val declaredClass = declaredType.asClass()
|
||||
val actualType: Type = if (actualClass == null) declaredType
|
||||
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||
val ourType = MemoType(actualClass, declaredType)
|
||||
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
|
||||
return serializersByType[ourType] ?: run {
|
||||
|
||||
val serializer = when {
|
||||
val declaredClass = declaredType.asClass()
|
||||
val actualType: Type = if (actualClass == null) declaredType
|
||||
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||
|
||||
val serializer = when {
|
||||
// Declared class may not be set to Collection, but actual class could be a collection.
|
||||
// In this case use of CollectionSerializer is perfectly appropriate.
|
||||
(Collection::class.java.isAssignableFrom(declaredClass) ||
|
||||
(actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) &&
|
||||
!EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
|
||||
val declaredTypeAmended = CollectionSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
|
||||
serializersByType.computeIfAbsent(declaredTypeAmended) {
|
||||
CollectionSerializer(declaredTypeAmended, this)
|
||||
(Collection::class.java.isAssignableFrom(declaredClass) ||
|
||||
(actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) &&
|
||||
!EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
|
||||
val declaredTypeAmended = CollectionSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
|
||||
serializersByType.computeIfAbsent(declaredTypeAmended) {
|
||||
CollectionSerializer(declaredTypeAmended, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Declared class may not be set to Map, but actual class could be a map.
|
||||
// In this case use of MapSerializer is perfectly appropriate.
|
||||
(Map::class.java.isAssignableFrom(declaredClass) ||
|
||||
(actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> {
|
||||
val declaredTypeAmended = MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
|
||||
serializersByType.computeIfAbsent(declaredTypeAmended) {
|
||||
makeMapSerializer(declaredTypeAmended)
|
||||
}
|
||||
}
|
||||
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
|
||||
logger.trace {
|
||||
"class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " +
|
||||
"declaredType=${declaredType.typeName} " +
|
||||
"isEnum=${declaredType::class.java.isEnum}"
|
||||
(Map::class.java.isAssignableFrom(declaredClass) ||
|
||||
(actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> {
|
||||
val declaredTypeAmended = MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
|
||||
serializersByType.computeIfAbsent(declaredTypeAmended) {
|
||||
makeMapSerializer(declaredTypeAmended)
|
||||
}
|
||||
}
|
||||
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
|
||||
logger.trace {
|
||||
"class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " +
|
||||
"declaredType=${declaredType.typeName} " +
|
||||
"isEnum=${declaredType::class.java.isEnum}"
|
||||
}
|
||||
|
||||
serializersByType.computeIfAbsent(actualClass ?: declaredClass) {
|
||||
whitelist.requireWhitelisted(actualType)
|
||||
EnumSerializer(actualType, actualClass ?: declaredClass, this)
|
||||
serializersByType.computeIfAbsent(actualClass ?: declaredClass) {
|
||||
whitelist.requireWhitelisted(actualType)
|
||||
EnumSerializer(actualType, actualClass ?: declaredClass, this)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
|
||||
}
|
||||
|
||||
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
|
||||
// Always store the short-circuit too, for performance.
|
||||
serializersByType.putIfAbsent(ourType, serializer)
|
||||
return serializer
|
||||
}
|
||||
|
||||
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
|
||||
|
||||
return serializer
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,9 +63,9 @@ class StaticInitialisationOfSerializedObjectTest {
|
||||
// we can't actually construct one
|
||||
sf.get(null, D::class.java)
|
||||
|
||||
// post creation of the serializer we should have one element in the map, this
|
||||
// post creation of the serializer we should have two elements in the map, this
|
||||
// proves we didn't statically construct an instance of C when building the serializer
|
||||
assertEquals(1, serialisersByType.size)
|
||||
assertEquals(2, serialisersByType.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.bootstrapper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
@ -9,6 +11,7 @@ import javafx.stage.StageStyle;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class GuiUtils {
|
||||
|
||||
@ -18,31 +21,40 @@ public class GuiUtils {
|
||||
alert.setTitle("Exception");
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
exception.printStackTrace(pw);
|
||||
String exceptionText = sw.toString();
|
||||
|
||||
Label label = new Label("Details:");
|
||||
|
||||
TextArea textArea = new TextArea(exceptionText);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
|
||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||
|
||||
GridPane expContent = new GridPane();
|
||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||
expContent.add(label, 0, 0);
|
||||
expContent.add(textArea, 0, 1);
|
||||
|
||||
alert.getDialogPane().setExpandableContent(expContent);
|
||||
|
||||
if (exception != null) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
exception.printStackTrace(pw);
|
||||
String exceptionText = sw.toString();
|
||||
TextArea textArea = new TextArea(exceptionText);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||
Label label = new Label("Details:");
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||
GridPane expContent = new GridPane();
|
||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||
expContent.add(label, 0, 0);
|
||||
expContent.add(textArea, 0, 1);
|
||||
alert.getDialogPane().setExpandableContent(expContent);
|
||||
}
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
public static void showAndQuit(String title, String message, Throwable exception) {
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
new JFXPanel();
|
||||
Platform.runLater(() -> {
|
||||
showException(title, message, exception);
|
||||
countDownLatch.countDown();
|
||||
System.exit(1);
|
||||
});
|
||||
try {
|
||||
countDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@file:JvmName("Main")
|
||||
|
||||
package net.corda.bootstrapper
|
||||
|
||||
import javafx.application.Application
|
||||
@ -7,15 +8,19 @@ import net.corda.bootstrapper.backends.Backend.BackendType.AZURE
|
||||
import net.corda.bootstrapper.cli.AzureParser
|
||||
import net.corda.bootstrapper.cli.CliParser
|
||||
import net.corda.bootstrapper.cli.CommandLineInterface
|
||||
import net.corda.bootstrapper.docker.DockerUtils
|
||||
import net.corda.bootstrapper.gui.Gui
|
||||
import net.corda.bootstrapper.serialization.SerializationEngine
|
||||
import picocli.CommandLine
|
||||
import javax.ws.rs.ProcessingException
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
val baseArgs = CliParser()
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
SerializationEngine.init()
|
||||
CommandLine(baseArgs).parse(*args)
|
||||
testDockerConnectivity()
|
||||
|
||||
if (baseArgs.gui) {
|
||||
Application.launch(Gui::class.java)
|
||||
@ -32,3 +37,16 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
CommandLineInterface().run(argParser)
|
||||
}
|
||||
|
||||
private fun testDockerConnectivity() {
|
||||
try {
|
||||
DockerUtils.createLocalDockerClient().listImagesCmd().exec()
|
||||
} catch (se: ProcessingException) {
|
||||
if (baseArgs.gui) {
|
||||
GuiUtils.showAndQuit("Could not connect to Docker", "Please ensure that docker is running locally", null)
|
||||
} else {
|
||||
System.err.println("Could not connect to Docker, please ensure that docker is running locally")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.bootstrapper.gui
|
||||
|
||||
import javafx.stage.Stage
|
||||
import tornadofx.*
|
||||
import tornadofx.App
|
||||
|
||||
class Gui : App(BootstrapperView::class) {
|
||||
override fun start(stage: Stage) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user