Merge remote-tracking branch 'open/master' into os-merge-b97af47

# Conflicts:
#	.idea/compiler.xml
#	docs/source/changelog.rst
#	docs/source/release-notes.rst
#	node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt
#	tools/bootstrapper/build.gradle
This commit is contained in:
Shams Asari 2018-06-25 11:38:30 +01:00
commit 98cd8be160
57 changed files with 1136 additions and 669 deletions

View File

@ -98,6 +98,7 @@ buildscript {
ext.snappy_version = '0.4'
ext.fast_classpath_scanner_version = '2.12.3'
ext.jcabi_manifests_version = '1.1'
ext.picocli_version = '3.0.0'
ext.deterministic_rt_version = '1.0-SNAPSHOT'
// Name of the IntelliJ SDK created for the deterministic Java rt.jar.

View File

@ -50,7 +50,7 @@ class AMQPClientSerializationScheme(
target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader()).apply {
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply {
register(RpcClientObservableSerializer)
register(RpcClientCordaFutureSerializer(this))
register(RxNotificationSerializer(this))

View File

@ -71,12 +71,18 @@ data class NotarisationPayload(val transaction: Any, val requestSignature: Notar
* A helper for automatically casting the underlying [transaction] payload to a [SignedTransaction].
* Should only be used by validating notaries.
*/
val signedTransaction get() = transaction as SignedTransaction
val signedTransaction get() = transaction as? SignedTransaction ?: throw exception()
/**
* A helper for automatically casting the underlying [transaction] payload to a [CoreTransaction].
* Should only be used by non-validating notaries.
*/
val coreTransaction get() = transaction as CoreTransaction
val coreTransaction get() = transaction as? CoreTransaction ?: throw exception()
private fun exception() = IllegalArgumentException("Unexpected transaction type in the notarisation payload: " +
"${transaction::class.java}, it may be that there is a discrepancy between the configured notary type " +
"(validating/non-validating) and the one advertised on the network parameters."
)
}
/** Payload returned by the notary service flow to the client. */

View File

@ -24,6 +24,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.*
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
@ -396,12 +397,19 @@ fun <T, U : T> uncheckedCast(obj: T) = obj as U
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
/** Provide access to internal method for AttachmentClassLoaderTests */
@DeleteForDJVM fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
@DeleteForDJVM
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(services, serializationContext)
}
/** Provide access to internal method for AttachmentClassLoaderTests */
@DeleteForDJVM fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
@DeleteForDJVM
fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
return toLedgerTransactionWithContext(services, serializationContext)
}
/** Returns the location of this class. */
val Class<*>.location: URL get() = protectionDomain.codeSource.location
/** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName: String get() = java.packageName

View File

@ -81,6 +81,9 @@ fun Path.lastModifiedTime(vararg options: LinkOption): FileTime = Files.getLastM
/** @see Files.isDirectory */
fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
/** @see Files.isSameFile */
fun Path.isSameAs(other: Path): Boolean = Files.isSameFile(this, other)
/**
* Same as [Files.list] except it also closes the [Stream].
* @return the output of [block]

View File

@ -152,6 +152,14 @@ interface SerializationContext {
* otherwise they appear as new copies of the object.
*/
val objectReferencesEnabled: Boolean
/**
* If true the carpenter will happily synthesis classes that implement interfaces containing methods that are not
* getters for any AMQP fields. Invoking these methods will throw an [AbstractMethodError]. If false then an exception
* will be thrown during deserialization instead.
*
* The default is false.
*/
val lenientCarpenterEnabled: Boolean
/**
* The use case we are serializing or deserializing for. See [UseCase].
*/
@ -167,6 +175,12 @@ interface SerializationContext {
*/
fun withoutReferences(): SerializationContext
/**
* Return a new context based on this one but with a lenient carpenter.
* @see lenientCarpenterEnabled
*/
fun withLenientCarpenter(): SerializationContext
/**
* Helper method to return a new context based on this context with the deserialization class loader changed.
*/

View File

@ -9,6 +9,10 @@ Unreleased
* Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code.
* The class carpenter has a "lenient" mode where it will, during deserialisation, happily synthesis classes that implement
interfaces that will have unimplemented methods. This is useful, for example, for object viewers. This can be turned on
with ``SerializationContext.withLenientCarpenter``.
* Introduced a grace period before the initial node registration fails if the node cannot connect to the Doorman.
It retries 10 times with a 1 minute interval in between each try. At the moment this is not configurable.
@ -273,7 +277,7 @@ Corda Enterprise 3.0 Developer Preview
* Cordform (which is the ``deployNodes`` gradle task) does this copying automatically for the demos. The ``NetworkMap``
parameter is no longer needed.
* For test deployments we've introduced a bootstrapping tool (see :doc:`setting-up-a-corda-network`).
* For test deployments we've introduced a bootstrapping tool (see :doc:`network-bootstrapper`).
* ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` configs have been
removed. The configuration of notaries has been simplified into a single ``notary`` config object. See

View File

@ -238,6 +238,14 @@ latex_documents = [('index', u'corda-developer-site.tex', u'Corda Developer Docu
# If false, no module index is generated.
# latex_domain_indices = True
_PREAMBLE = r"""
\usepackage[utf8]{inputenc}
\usepackage{pmboxdraw}
\DeclareUnicodeCharacter{2514}{\textSFii}
\DeclareUnicodeCharacter{251C}{\textSFviii}
\DeclareUnicodeCharacter{2705}{\checkmark}
"""
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
@ -251,7 +259,9 @@ latex_elements = {
# Latex figure (float) alignment
# 'figure_align': 'htbp',
'maxlistdepth': 2000,
'maxlistdepth' : '2000',
'extraclassoptions': 'openany',
'preamble' : _PREAMBLE,
'extraclassoptions' : 'openany',
}

View File

@ -0,0 +1,101 @@
Network Bootstrapper
====================
Test deployments
~~~~~~~~~~~~~~~~
Nodes within a network see each other using the network map. This is a collection of statically signed node-info files,
one for each node. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
in its local network map cache. The node generates its own node-info file on startup.
In addition to the network map, all the nodes must also use the same set of network parameters. These are a set of constants
which guarantee interoperability between the nodes. The HTTP network map distributes the network parameters which are downloaded
automatically by the nodes. In the absence of this the network parameters must be generated locally.
For these reasons, test deployments can avail themselves of the network bootstrapper. This is a tool that scans all the
node configurations from a common directory to generate the network parameters file, which is then copied to all the nodes'
directories. It also copies each node's node-info file to every other node so that they can all be visible to each other.
You can find out more about network maps and network parameters from :doc:`network-map`.
Bootstrapping a test network
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The bootstrapper can be downloaded from https://downloads.corda.net/network-bootstrapper-VERSION.jar, where ``VERSION``
is the Corda version.
Create a directory containing a node config file, ending in "_node.conf", for each node you want to create. Then run the
following command:
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
For example running the command on a directory containing these files:
.. sourcecode:: none
.
├── notary_node.conf // The notary's node.conf file
├── partya_node.conf // Party A's node.conf file
└── partyb_node.conf // Party B's node.conf file
will generate directories containing three nodes: ``notary``, ``partya`` and ``partyb``. They will each use the ``corda.jar``
that comes with the bootstrapper. If a different version of Corda is required then simply place that ``corda.jar`` file
alongside the configuration files in the directory.
The directory can also contain CorDapp JARs which will be copied to each node's ``cordapps`` directory.
You can also have the node directories containing their "node.conf" files already laid out. The previous example would be:
.. sourcecode:: none
.
├── notary
│   └── node.conf
├── partya
│   └── node.conf
└── partyb
└── node.conf
Similarly, each node directory may contain its own ``corda.jar``, which the bootstrapper will use instead.
Synchronisation
~~~~~~~~~~~~~~~
This tool only bootstraps a network. It cannot dynamically update if a new node needs to join the network or if an existing
one has changed something in their node-info, e.g. their P2P address. For this the new node-info file will need to be placed
in the other nodes' ``additional-node-infos`` directory. A simple way to do this is to use `rsync <https://en.wikipedia.org/wiki/Rsync>`_.
However, if it's known beforehand the set of nodes that will eventually form part of the network then all the node directories
can be pregenerated in the bootstrap and only started when needed.
Running the bootstrapper again on the same network will allow a new node to be added or an existing one to have its updated
node-info re-distributed. However this comes at the expense of having to temporarily collect the node directories back
together again under a common parent directory.
Whitelisting contracts
~~~~~~~~~~~~~~~~~~~~~~
The CorDapp JARs are also automatically used to create the *Zone whitelist* (see :doc:`api-contract-constraints`) for
the network.
.. note:: If you only wish to whitelist the CorDapps but not copy them to each node then run with the ``--no-copy`` flag.
The CorDapp JARs will be hashed and scanned for ``Contract`` classes. These contract class implementations will become part
of the whitelisted contracts in the network parameters (see ``NetworkParameters.whitelistedContractImplementations`` :doc:`network-map`).
If the network already has a set of network parameters defined (i.e. the node directories all contain the same network-parameters
file) then the new set of contracts will be appended to the current whitelist.
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
By default the bootstrapper will whitelist all the contracts found in all the CorDapp JARs. To prevent certain
contracts from being whitelisted, add their fully qualified class name in the ``exclude_whitelist.txt``. These will instead
use the more restrictive ``HashAttachmentConstraint``.
For example:
.. sourcecode:: none
net.corda.finance.contracts.asset.Cash
net.corda.finance.contracts.asset.CommercialPaper

View File

@ -72,7 +72,7 @@ the network, along with the network parameters file and identity certificates. G
online at once - an offline node that isn't being interacted with doesn't impact the network in any way. So a test
cluster generated like this can be sized for the maximum size you may need, and then scaled up and down as necessary.
More information can be found in :doc:`setting-up-a-corda-network`.
More information can be found in :doc:`network-bootstrapper`.
Network parameters
------------------

View File

@ -564,6 +564,11 @@ without the supporting classes being present on the classpath. This can be usef
be able to use reflection over the deserialized data, for scripting languages that run on the JVM, and also for
ensuring classes not on the classpath can be deserialized without loading potentially malicious code.
If the original class implements some interfaces then the carpenter will make sure that all of the interface methods are
backed by feilds. If that's not the case then an exception will be thrown during deserialization. This check can
be turned off with ``SerializationContext.withLenientCarpenter``. This can be useful if only the field getters are needed,
say in an object viewer.
Possible future enhancements include:
#. Java singleton support. We will add support for identifying classes which are singletons and identifying the

View File

@ -46,81 +46,14 @@ The most important fields regarding network configuration are:
and ``rpcAddress`` if they are on the same machine.
* ``notary.serviceLegalName``: The name of the notary service, required to setup distributed notaries with the network-bootstrapper.
Bootstrapping the network
~~~~~~~~~~~~~~~~~~~~~~~~~
The nodes see each other using the network map. This is a collection of statically signed node-info files, one for each
node in the network. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
in its local network map cache. The node generates its own node-info file on startup.
In addition to the network map, all the nodes on a network must use the same set of network parameters. These are a set
of constants which guarantee interoperability between nodes. The HTTP network map distributes the network parameters
which the node downloads automatically. In the absence of this the network parameters must be generated locally. This can
be done with the network bootstrapper. This is a tool that scans all the node configurations from a common directory to
generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file
to every other node so that they can all transact with each other.
The bootstrapper tool can be downloaded from https://downloads.corda.net/network-bootstrapper-corda-X.Y.jar, where ``X``
is the major Corda version and ``Y`` is the minor Corda version.
To use it, create a directory containing a node config file, ending in "_node.conf", for each node you want to create.
Then run the following command:
``java -jar network-bootstrapper-corda-X.Y.jar <nodes-root-dir>``
For example running the command on a directory containing these files :
.. sourcecode:: none
.
├── notary_node.conf // The notary's node.conf file
├── partya_node.conf // Party A's node.conf file
└── partyb_node.conf // Party B's node.conf file
Would generate directories containing three nodes: notary, partya and partyb.
This tool only bootstraps a network. It cannot dynamically update if a new node needs to join the network or if an existing
one has changed something in their node-info, e.g. their P2P address. For this the new node-info file will need to be placed
in the other nodes' ``additional-node-infos`` directory. A simple way to do this is to use `rsync <https://en.wikipedia.org/wiki/Rsync>`_.
However, if it's known beforehand the set of nodes that will eventually the node folders can be pregenerated in the bootstrap
and only started when needed.
Whitelisting Contracts
~~~~~~~~~~~~~~~~~~~~~~
If you want to create a *Zone whitelist* (see :doc:`api-contract-constraints`), you can pass in a list of CorDapp jars:
``java -jar network-bootstrapper.jar <nodes-root-dir> <1st CorDapp jar> <2nd CorDapp jar> ..``
The CorDapp jars will be hashed and scanned for ``Contract`` classes. These contract class implementations will become part
of the whitelisted contracts in the network parameters (see ``NetworkParameters.whitelistedContractImplementations`` :doc:`network-map`).
If the network already has a set of network parameters defined (i.e. the node directories all contain the same network-parameters
file) then the new set of contracts will be appended to the current whitelist.
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
By default the bootstrapper tool will whitelist all the contracts found in all the CorDapp jars. To prevent certain
contracts from being whitelisted, add their fully qualified class name in the ``exclude_whitelist.txt``. These will instead
use the more restrictive ``HashAttachmentConstraint``.
For example:
.. sourcecode:: none
net.corda.finance.contracts.asset.Cash
net.corda.finance.contracts.asset.CommercialPaper
In addition to using the CorDapp jars to update the whitelist, the bootstrapper will also copy them to all the nodes'
``cordapps`` directory.
Starting the nodes
~~~~~~~~~~~~~~~~~~
You may now start the nodes in any order. You should see a banner, some log lines and eventually ``Node started up and registered``,
indicating that the node is fully started.
You will first need to create the local network by bootstrapping it with the bootstrapper. Details of how to do that can
be found in :doc:`network-bootstrapper`.
Once that's done you may now start the nodes in any order. You should see a banner, some log lines and eventually
``Node started up and registered``, indicating that the node is fully started.
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.

View File

@ -4,6 +4,7 @@ Tools
.. toctree::
:maxdepth: 1
network-bootstrapper
blob-inspector
demobench
node-explorer

View File

@ -418,7 +418,7 @@ be moved to another machine open its config file and change the Artemis messagin
where the node will run (e.g. ``p2pAddress="10.18.0.166:10007"``).
These changes require new node-info files to be distributed amongst the nodes. Use the network bootstrapper tool
(see :doc:`setting-up-a-corda-network` for more information on this and how to built it) to update the files and have
(see :doc:`network-bootstrapper` for more information on this and how to built it) to update the files and have
them distributed locally.
``java -jar network-bootstrapper.jar kotlin-source/build/nodes``

View File

@ -135,7 +135,7 @@ With the re-designed network map service the following changes need to be made:
* The network map is no longer provided by a node and thus the ``networkMapService`` config is ignored. Instead the
network map is either provided by the compatibility zone (CZ) operator (who operates the doorman) and available
using the ``compatibilityZoneURL`` config, or is provided using signed node info files which are copied locally.
See :doc:`network-map` for more details, and :doc:`setting-up-a-corda-network` on how to use the network
See :doc:`network-map` for more details, and :doc:`network-bootstrapper` on how to use the network
bootstrapper for deploying a local network.
* Configuration for a notary has been simplified. ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses``

View File

@ -50,7 +50,7 @@ curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgr
curl -L "https://github.com/Microsoft/mssql-jdbc/releases/download/v6.2.2/mssql-jdbc-6.2.2.jre8.jar" > ${DRIVERS_DIR}/mssql-jdbc-6.2.2.jre8.jar
# Build Network Bootstrapper
./gradlew buildBootstrapperJar
./gradlew tools:bootstrapper:jar
cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) ${CORDA_DIR}/network-bootstrapper.jar
# TODO: resolve Doorman/NMS artifacts from new artifactory location.

View File

@ -37,8 +37,8 @@ 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.InputStream
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.time.Instant
import java.util.*
@ -53,7 +53,21 @@ import kotlin.streams.toList
/**
* Class to bootstrap a local network of Corda nodes on the same filesystem.
*/
class NetworkBootstrapper {
// TODO Move this to tools:bootstrapper
class NetworkBootstrapper
@VisibleForTesting
internal constructor(private val initSerEnv: Boolean,
private val embeddedCordaJar: () -> InputStream,
private val nodeInfosGenerator: (List<Path>) -> List<Path>,
private val contractsJarConverter: (Path) -> ContractsJar) {
constructor() : this(
initSerEnv = true,
embeddedCordaJar = Companion::extractEmbeddedCordaJar,
nodeInfosGenerator = Companion::generateNodeInfos,
contractsJarConverter = ::ContractsJarFile
)
companion object {
// TODO This will probably need to change once we start using a bundled JVM
private val nodeInfoGenCmd = listOf(
@ -65,11 +79,42 @@ class NetworkBootstrapper {
private const val LOGS_DIR_NAME = "logs"
@JvmStatic
fun main(args: Array<String>) {
val baseNodeDirectory = requireNotNull(args.firstOrNull()) { "Expecting first argument which is the nodes' parent directory" }
val cordappJars = if (args.size > 1) args.asList().drop(1).map { Paths.get(it) } else emptyList()
NetworkBootstrapper().bootstrap(Paths.get(baseNodeDirectory).toAbsolutePath().normalize(), cordappJars)
private fun extractEmbeddedCordaJar(): InputStream {
return Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar")
}
private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> {
val numParallelProcesses = Runtime.getRuntime().availableProcessors()
val timePerNode = 40.seconds // On the test machine, generating the node info takes 7 seconds for a single node.
val tExpected = maxOf(timePerNode, timePerNode * nodeDirs.size.toLong() / numParallelProcesses.toLong())
val warningTimer = Timer("WarnOnSlowMachines", false).schedule(tExpected.toMillis()) {
println("... still waiting. If this is taking longer than usual, check the node logs.")
}
val executor = Executors.newFixedThreadPool(numParallelProcesses)
return try {
nodeDirs.map { executor.fork { generateNodeInfo(it) } }.transpose().getOrThrow()
} finally {
warningTimer.cancel()
executor.shutdownNow()
}
}
private fun generateNodeInfo(nodeDir: Path): Path {
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
val process = ProcessBuilder(nodeInfoGenCmd)
.directory(nodeDir.toFile())
.redirectErrorStream(true)
.redirectOutput((logsDir / "node-info-gen.log").toFile())
.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.")
}
check(process.exitValue() == 0) { "Error while generating node info file. Please check the logs in $logsDir." }
return nodeDir.list { paths ->
paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
}
}
}
@ -102,29 +147,61 @@ class NetworkBootstrapper {
private fun generateServiceIdentitiesForNotaryClusters(configs: Map<Path, Config>) {
notaryClusters(configs).forEach { (cluster, directories) ->
when (cluster) {
is NotaryCluster.BFT ->
DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(directories, cluster.name, threshold = 1 + 2 * directories.size / 3)
is NotaryCluster.CFT ->
DevIdentityGenerator.generateDistributedNotarySingularIdentity(directories, cluster.name)
is NotaryCluster.BFT -> DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(
directories,
cluster.name,
threshold = 1 + 2 * directories.size / 3
)
is NotaryCluster.CFT -> DevIdentityGenerator.generateDistributedNotarySingularIdentity(directories, cluster.name)
}
}
}
/** Entry point for Cordform */
fun bootstrap(directory: Path, cordappJars: List<Path>) {
bootstrap(directory, cordappJars, copyCordapps = true, fromCordform = true)
}
/** Entry point for the tool */
fun bootstrap(directory: Path, copyCordapps: Boolean) {
// Don't accidently include the bootstrapper jar as a CorDapp!
val bootstrapperJar = javaClass.location.toPath()
val cordappJars = directory.list { paths ->
paths.filter { it.toString().endsWith(".jar") && !it.isSameAs(bootstrapperJar) && it.fileName.toString() != "corda.jar" }.toList()
}
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false)
}
private fun bootstrap(directory: Path, cordappJars: List<Path>, copyCordapps: Boolean, fromCordform: Boolean) {
directory.createDirectories()
println("Bootstrapping local network in $directory")
generateDirectoriesIfNeeded(directory, cordappJars)
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
println("Bootstrapping local test network in $directory")
if (!fromCordform) {
println("Found the following CorDapps: ${cordappJars.map { it.fileName }}")
}
createNodeDirectoriesIfNeeded(directory, fromCordform)
val nodeDirs = gatherNodeDirectories(directory)
require(nodeDirs.isNotEmpty()) { "No nodes found" }
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
if (!fromCordform) {
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
}
val configs = nodeDirs.associateBy({ it }, { ConfigFactory.parseFile((it / "node.conf").toFile()) })
checkForDuplicateLegalNames(configs.values)
if (copyCordapps && cordappJars.isNotEmpty()) {
println("Copying CorDapp JARs into node directories")
for (nodeDir in nodeDirs) {
val cordappsDir = (nodeDir / "cordapps").createDirectories()
cordappJars.forEach { it.copyToDirectory(cordappsDir) }
}
}
generateServiceIdentitiesForNotaryClusters(configs)
initialiseSerialization()
if (initSerEnv) {
initialiseSerialization()
}
try {
println("Waiting for all nodes to generate their node-info files...")
val nodeInfoFiles = generateNodeInfos(nodeDirs)
println("Checking for duplicate nodes")
checkForDuplicateLegalNames(nodeInfoFiles)
val nodeInfoFiles = nodeInfosGenerator(nodeDirs)
println("Distributing all node-info files to all nodes")
distributeNodeInfos(nodeDirs, nodeInfoFiles)
print("Loading existing network parameters... ")
@ -133,72 +210,70 @@ class NetworkBootstrapper {
println("Gathering notary identities")
val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs)
println("Generating contract implementations whitelist")
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.map(::ContractsJarFile))
val netParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs)
println("${if (existingNetParams == null) "New" else "Updated"} $netParams")
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.map(contractsJarConverter))
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs)
if (newNetParams != existingNetParams) {
println("${if (existingNetParams == null) "New" else "Updated"} $newNetParams")
} else {
println("Network parameters unchanged")
}
println("Bootstrapping complete!")
} finally {
_contextSerializationEnv.set(null)
if (initSerEnv) {
_contextSerializationEnv.set(null)
}
}
}
private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> {
val numParallelProcesses = Runtime.getRuntime().availableProcessors()
val timePerNode = 40.seconds // On the test machine, generating the node info takes 7 seconds for a single node.
val tExpected = maxOf(timePerNode, timePerNode * nodeDirs.size.toLong() / numParallelProcesses.toLong())
val warningTimer = Timer("WarnOnSlowMachines", false).schedule(tExpected.toMillis()) {
println("...still waiting. If this is taking longer than usual, check the node logs.")
private fun createNodeDirectoriesIfNeeded(directory: Path, fromCordform: Boolean) {
val cordaJar = directory / "corda.jar"
var usingEmbedded = false
if (!cordaJar.exists()) {
embeddedCordaJar().use { it.copyTo(cordaJar) }
usingEmbedded = true
} else if (!fromCordform) {
println("Using corda.jar in root directory")
}
val executor = Executors.newFixedThreadPool(numParallelProcesses)
return try {
nodeDirs.map { executor.fork { generateNodeInfo(it) } }.transpose().getOrThrow()
} finally {
warningTimer.cancel()
executor.shutdownNow()
}
}
private fun generateNodeInfo(nodeDir: Path): Path {
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
val process = ProcessBuilder(nodeInfoGenCmd)
.directory(nodeDir.toFile())
.redirectErrorStream(true)
.redirectOutput((logsDir / "node-info-gen.log").toFile())
.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.")
}
check(process.exitValue() == 0) { "Error while generating node info file. Please check the logs in $logsDir." }
return nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() }
}
private fun generateDirectoriesIfNeeded(directory: Path, cordappJars: List<Path>) {
val confFiles = directory.list { it.filter { it.toString().endsWith("_node.conf") }.toList() }
val webServerConfFiles = directory.list { it.filter { it.toString().endsWith("_web-server.conf") }.toList() }
if (confFiles.isEmpty()) return
println("Node config files found in the root directory - generating node directories and copying CorDapp jars into them")
val cordaJar = extractCordaJarTo(directory)
for (confFile in confFiles) {
val nodeName = confFile.fileName.toString().removeSuffix("_node.conf")
println("Generating directory for $nodeName")
println("Generating node directory for $nodeName")
val nodeDir = (directory / nodeName).createDirectories()
confFile.moveTo(nodeDir / "node.conf", REPLACE_EXISTING)
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.moveTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
confFile.copyTo(nodeDir / "node.conf", REPLACE_EXISTING)
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.copyTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
val cordappsDir = (nodeDir / "cordapps").createDirectories()
cordappJars.forEach { it.copyToDirectory(cordappsDir) }
}
cordaJar.delete()
directory.list { paths ->
paths.filter { (it / "node.conf").exists() && !(it / "corda.jar").exists() }.forEach {
println("Copying corda.jar into node directory ${it.fileName}")
cordaJar.copyToDirectory(it)
}
}
if (fromCordform) {
confFiles.forEach(Path::delete)
webServerConfFiles.forEach(Path::delete)
}
if (fromCordform || usingEmbedded) {
cordaJar.delete()
}
}
private fun extractCordaJarTo(directory: Path): Path {
val cordaJarPath = directory / "corda.jar"
if (!cordaJarPath.exists()) {
Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar").use { it.copyTo(cordaJarPath) }
private fun gatherNodeDirectories(directory: Path): List<Path> {
return directory.list { paths ->
paths.filter {
val exists = (it / "corda.jar").exists()
if (exists) {
require((it / "node.conf").exists()) { "Missing node.conf in node directory ${it.fileName}" }
}
exists
}.toList()
}
return cordaJarPath
}
private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) {
@ -210,20 +285,13 @@ class NetworkBootstrapper {
}
}
/*the function checks for duplicate myLegalName in the all the *_node.conf files
All the myLegalName values are added to a HashSet - this helps detect duplicate values.
If a duplicate name is found the process is aborted with an error message
*/
private fun checkForDuplicateLegalNames(nodeInfoFiles: List<Path>) {
val legalNames = HashSet<String>()
for (nodeInfoFile in nodeInfoFiles) {
val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
val legalName = nodeConfig.getString("myLegalName")
if(!legalNames.add(legalName)){
println("Duplicate Node Found - ensure every node has a unique legal name");
throw IllegalArgumentException("Duplicate Node Found - $legalName");
private fun checkForDuplicateLegalNames(nodeConfigs: Collection<Config>) {
val duplicateLegalNames = nodeConfigs
.groupBy { it.getString("myLegalName") }
.mapNotNull { if (it.value.size > 1) it.key else null }
check(duplicateLegalNames.isEmpty()) {
"Nodes must have unique legal names. The following are used more than once: $duplicateLegalNames"
}
}
}
private fun gatherNotaryInfos(nodeInfoFiles: List<Path>, configs: Map<Path, Config>): List<NotaryInfo> {
@ -274,15 +342,19 @@ class NetworkBootstrapper {
whitelist: Map<String, List<AttachmentId>>,
existingNetParams: NetworkParameters?,
nodeDirs: List<Path>): NetworkParameters {
val networkParameters = if (existingNetParams != null) {
existingNetParams.copy(
notaries = notaryInfos,
modifiedTime = Instant.now(),
whitelistedContractImplementations = whitelist,
epoch = existingNetParams.epoch + 1
)
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
val netParams = if (existingNetParams != null) {
if (existingNetParams.whitelistedContractImplementations == whitelist && existingNetParams.notaries == notaryInfos) {
existingNetParams
} else {
existingNetParams.copy(
notaries = notaryInfos,
modifiedTime = Instant.now(),
whitelistedContractImplementations = whitelist,
epoch = existingNetParams.epoch + 1
)
}
} else {
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
NetworkParameters(
minimumPlatformVersion = 1,
notaries = notaryInfos,
@ -294,9 +366,9 @@ class NetworkBootstrapper {
eventHorizon = 30.days
)
}
val copier = NetworkParametersCopier(networkParameters, overwriteFile = true)
val copier = NetworkParametersCopier(netParams, overwriteFile = true)
nodeDirs.forEach(copier::install)
return networkParameters
return netParams
}
private fun NodeInfo.notaryIdentity(): Party {
@ -311,7 +383,6 @@ class NetworkBootstrapper {
}
// We need to to set serialization env, because generation of parameters is run from Cordform.
// KryoServerSerializationScheme is not accessible from nodeapi.
private fun initialiseSerialization() {
_contextSerializationEnv.set(SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {

View File

@ -1,141 +1,289 @@
package net.corda.nodeapi.internal.network
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentId
import net.corda.nodeapi.internal.ContractsJar
import net.corda.testing.common.internal.testNetworkParameters
import com.typesafe.config.ConfigFactory
import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.crypto.sha256
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.createNodeInfoAndSigned
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import kotlin.streams.toList
class NetworkBootstrapperTest {
@Test
fun `no jars against empty whitelist`() {
val whitelist = generateWhitelist(emptyMap(), emptyList(), emptyList())
assertThat(whitelist).isEmpty()
}
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Test
fun `no jars against single whitelist`() {
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), emptyList())
assertThat(newWhitelist).isEqualTo(existingWhitelist)
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `empty jar against empty whitelist`() {
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
assertThat(whitelist).isEmpty()
}
private val fakeEmbeddedCordaJar = fakeFileBytes()
@Test
fun `empty jar against single whitelist`() {
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
assertThat(newWhitelist).isEqualTo(existingWhitelist)
}
private val contractsJars = HashMap<Path, TestContractsJar>()
@Test
fun `jar with single contract against empty whitelist`() {
val jar = TestContractsJar(contractClassNames = listOf("class1"))
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(jar))
assertThat(whitelist).isEqualTo(mapOf(
"class1" to listOf(jar.hash)
))
}
private val bootstrapper = NetworkBootstrapper(
initSerEnv = false,
embeddedCordaJar = fakeEmbeddedCordaJar::inputStream,
nodeInfosGenerator = { nodeDirs ->
nodeDirs.map { nodeDir ->
val name = nodeDir.fakeNodeConfig.myLegalName
val file = nodeDir / "$NODE_INFO_FILE_NAME_PREFIX${name.serialize().hash}"
if (!file.exists()) {
createNodeInfoAndSigned(name).signed.serialize().open().copyTo(file)
}
file
}
},
contractsJarConverter = { contractsJars[it]!! }
)
@Test
fun `single contract jar against single whitelist of different contract`() {
val class1JarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(class1JarHash))
val jar = TestContractsJar(contractClassNames = listOf("class2"))
val whitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
assertThat(whitelist).isEqualTo(mapOf(
"class1" to listOf(class1JarHash),
"class2" to listOf(jar.hash)
))
}
private val aliceConfig = FakeNodeConfig(ALICE_NAME)
private val bobConfig = FakeNodeConfig(BOB_NAME)
private val notaryConfig = FakeNodeConfig(DUMMY_NOTARY_NAME, NotaryConfig(validating = true))
@Test
fun `same jar with single contract`() {
val jarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(jarHash))
val jar = TestContractsJar(hash = jarHash, contractClassNames = listOf("class1"))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
assertThat(newWhitelist).isEqualTo(existingWhitelist)
}
private var providedCordaJar: ByteArray? = null
private val configFiles = HashMap<Path, String>()
@Test
fun `jar with updated contract`() {
val previousJarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1"))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
assertThat(newWhitelist).isEqualTo(mapOf(
"class1" to listOf(previousJarHash, newContractsJar.hash)
))
}
@Test
fun `jar with one existing contract and one new one`() {
val previousJarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
assertThat(newWhitelist).isEqualTo(mapOf(
"class1" to listOf(previousJarHash, newContractsJar.hash),
"class2" to listOf(newContractsJar.hash)
))
}
@Test
fun `two versions of the same contract`() {
val version1Jar = TestContractsJar(contractClassNames = listOf("class1"))
val version2Jar = TestContractsJar(contractClassNames = listOf("class1"))
val newWhitelist = generateWhitelist(emptyMap(), emptyList(), listOf(version1Jar, version2Jar))
assertThat(newWhitelist).isEqualTo(mapOf(
"class1" to listOf(version1Jar.hash, version2Jar.hash)
))
}
@Test
fun `jar with single new contract that's excluded`() {
val jar = TestContractsJar(contractClassNames = listOf("class1"))
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
assertThat(whitelist).isEmpty()
}
@Test
fun `jar with two new contracts, one of which is excluded`() {
val jar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
assertThat(whitelist).isEqualTo(mapOf(
"class2" to listOf(jar.hash)
))
}
@Test
fun `jar with updated contract but it's excluded`() {
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
val jar = TestContractsJar(contractClassNames = listOf("class1"))
assertThatIllegalArgumentException().isThrownBy {
generateWhitelist(existingWhitelist, listOf("class1"), listOf(jar))
@After
fun `check config files are preserved`() {
configFiles.forEach { file, text ->
assertThat(file).hasContent(text)
}
}
private fun generateWhitelist(existingWhitelist: Map<String, List<AttachmentId>>,
excludeContracts: List<ContractClassName>,
contractJars: List<TestContractsJar>): Map<String, List<AttachmentId>> {
return generateWhitelist(
testNetworkParameters(whitelistedContractImplementations = existingWhitelist),
excludeContracts,
contractJars
)
@After
fun `check provided corda jar is preserved`() {
if (providedCordaJar == null) {
// Make sure we clean up if we used the embedded jar
assertThat(rootDir / "corda.jar").doesNotExist()
} else {
// Make sure we don't delete it if it was provided by the user
assertThat(rootDir / "corda.jar").hasBinaryContent(providedCordaJar)
}
}
data class TestContractsJar(override val hash: SecureHash = SecureHash.randomSHA256(),
private val contractClassNames: List<ContractClassName>) : ContractsJar {
override fun scan(): List<ContractClassName> = contractClassNames
@Test
fun `empty dir`() {
assertThatThrownBy {
bootstrap()
}.hasMessage("No nodes found")
}
@Test
fun `single node conf file`() {
createNodeConfFile("node1", bobConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "node1" to bobConfig)
networkParameters.run {
assertThat(epoch).isEqualTo(1)
assertThat(notaries).isEmpty()
assertThat(whitelistedContractImplementations).isEmpty()
}
}
@Test
fun `node conf file and corda jar`() {
createNodeConfFile("node1", bobConfig)
val fakeCordaJar = fakeFileBytes(rootDir / "corda.jar")
bootstrap()
assertBootstrappedNetwork(fakeCordaJar, "node1" to bobConfig)
}
@Test
fun `single node directory with just node conf file`() {
createNodeDir("bob", bobConfig)
bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "bob" to bobConfig)
}
@Test
fun `single node directory with node conf file and corda jar`() {
val nodeDir = createNodeDir("bob", bobConfig)
val fakeCordaJar = fakeFileBytes(nodeDir / "corda.jar")
bootstrap()
assertBootstrappedNetwork(fakeCordaJar, "bob" to bobConfig)
}
@Test
fun `single node directory with just corda jar`() {
val nodeCordaJar = (rootDir / "alice").createDirectories() / "corda.jar"
val fakeCordaJar = fakeFileBytes(nodeCordaJar)
assertThatThrownBy {
bootstrap()
}.hasMessageStartingWith("Missing node.conf in node directory alice")
assertThat(nodeCordaJar).hasBinaryContent(fakeCordaJar) // Make sure the corda.jar is left untouched
}
@Test
fun `two node conf files, one of which is a notary`() {
createNodeConfFile("alice", aliceConfig)
createNodeConfFile("notary", notaryConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary")
}
@Test
fun `two node conf files with the same legal name`() {
createNodeConfFile("node1", aliceConfig)
createNodeConfFile("node2", aliceConfig)
assertThatThrownBy {
bootstrap()
}.hasMessageContaining("Nodes must have unique legal names")
}
@Test
fun `one node directory and one node conf file`() {
createNodeConfFile("alice", aliceConfig)
createNodeDir("bob", bobConfig)
bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig)
}
@Test
fun `node conf file and CorDapp jar`() {
createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").hasBinaryContent(cordappBytes)
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256())
))
}
@Test
fun `no copy CorDapps`() {
createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
bootstrap(copyCordapps = false)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").doesNotExist()
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256())
))
}
@Test
fun `add node to existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap()
val networkParameters1 = (rootDir / "alice").networkParameters
createNodeConfFile("bob", bobConfig)
bootstrap()
val networkParameters2 = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig)
assertThat(networkParameters1).isEqualTo(networkParameters2)
}
@Test
fun `add notary to existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap()
createNodeConfFile("notary", notaryConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary")
assertThat(networkParameters.epoch).isEqualTo(2)
}
private val rootDir get() = tempFolder.root.toPath()
private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
val bytes = secureRandomBytes(128)
writeToFile?.write(bytes)
return bytes
}
private fun bootstrap(copyCordapps: Boolean = true) {
providedCordaJar = (rootDir / "corda.jar").let { if (it.exists()) it.readAll() else null }
bootstrapper.bootstrap(rootDir, copyCordapps)
}
private fun createNodeConfFile(nodeDirName: String, config: FakeNodeConfig) {
writeNodeConfFile(rootDir / "${nodeDirName}_node.conf", config)
}
private fun createNodeDir(nodeDirName: String, config: FakeNodeConfig): Path {
val nodeDir = (rootDir / nodeDirName).createDirectories()
writeNodeConfFile(nodeDir / "node.conf", config)
return nodeDir
}
private fun writeNodeConfFile(file: Path, config: FakeNodeConfig) {
val configText = config.toConfig().root().render()
file.writeText(configText)
configFiles[file] = configText
}
private fun createFakeCordappJar(cordappName: String, contractClassNames: List<String>): ByteArray {
val cordappJarFile = rootDir / "$cordappName.jar"
val cordappBytes = fakeFileBytes(cordappJarFile)
contractsJars[cordappJarFile] = TestContractsJar(cordappBytes.sha256(), contractClassNames)
return cordappBytes
}
private val Path.networkParameters: NetworkParameters get() {
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>().verifiedNetworkMapCert(DEV_ROOT_CA.certificate)
}
private val Path.nodeInfoFile: Path get() {
return list { it.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.toList() }.single()
}
private val Path.nodeInfo: NodeInfo get() = nodeInfoFile.readObject<SignedNodeInfo>().verified()
private val Path.fakeNodeConfig: FakeNodeConfig get() {
return ConfigFactory.parseFile((this / "node.conf").toFile()).parseAs(FakeNodeConfig::class)
}
private fun assertBootstrappedNetwork(cordaJar: ByteArray, vararg nodes: Pair<String, FakeNodeConfig>): NetworkParameters {
val networkParameters = (rootDir / nodes[0].first).networkParameters
val allNodeInfoFiles = nodes.map { (rootDir / it.first).nodeInfoFile }.associateBy({ it }, { it.readAll() })
for ((nodeDirName, config) in nodes) {
val nodeDir = rootDir / nodeDirName
assertThat(nodeDir / "corda.jar").hasBinaryContent(cordaJar)
assertThat(nodeDir.fakeNodeConfig).isEqualTo(config)
assertThat(nodeDir.networkParameters).isEqualTo(networkParameters)
// Make sure all the nodes have all of each others' node-info files
allNodeInfoFiles.forEach { nodeInfoFile, bytes ->
assertThat(nodeDir / NODE_INFO_DIRECTORY / nodeInfoFile.fileName.toString()).hasBinaryContent(bytes)
}
}
return networkParameters
}
private fun NetworkParameters.assertContainsNotary(dirName: String) {
val notaryParty = (rootDir / dirName).nodeInfo.legalIdentities.single()
assertThat(notaries).hasSize(1)
notaries[0].run {
assertThat(validating).isTrue()
assertThat(identity.name).isEqualTo(notaryParty.name)
assertThat(identity.owningKey).isEqualTo(notaryParty.owningKey)
}
}
data class FakeNodeConfig(val myLegalName: CordaX500Name, val notary: NotaryConfig? = null)
}

View File

@ -0,0 +1,10 @@
package net.corda.nodeapi.internal.network
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.nodeapi.internal.ContractsJar
data class TestContractsJar(override val hash: SecureHash = SecureHash.randomSHA256(),
private val contractClassNames: List<ContractClassName>) : ContractsJar {
override fun scan(): List<ContractClassName> = contractClassNames
}

View File

@ -1,41 +1,135 @@
package net.corda.nodeapi.internal.network
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.nodeapi.internal.ContractsJar
import net.corda.core.node.services.AttachmentId
import net.corda.testing.common.internal.testNetworkParameters
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class WhitelistGeneratorTest {
@Test
fun `no jars against empty whitelist`() {
val whitelist = generateWhitelist(emptyMap(), emptyList(), emptyList())
assertThat(whitelist).isEmpty()
}
@Test
fun `whitelist generator builds the correct whitelist map`() {
// given
val jars = (0..9).map {
val index = it
mock<ContractsJar> {
val secureHash = SecureHash.randomSHA256()
on { scan() }.then {
listOf(index.toString())
}
on { hash }.then {
secureHash
}
}
}
fun `no jars against single whitelist`() {
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), emptyList())
assertThat(newWhitelist).isEqualTo(existingWhitelist)
}
// when
val result = generateWhitelist(null, emptyList(), jars)
@Test
fun `empty jar against empty whitelist`() {
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
assertThat(whitelist).isEmpty()
}
// then
jars.forEachIndexed { index, item ->
verify(item).scan()
val attachmentIds = requireNotNull(result[index.toString()])
assertEquals(1, attachmentIds.size)
assertTrue { attachmentIds.contains(item.hash) }
@Test
fun `empty jar against single whitelist`() {
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
assertThat(newWhitelist).isEqualTo(existingWhitelist)
}
@Test
fun `jar with single contract against empty whitelist`() {
val jar = TestContractsJar(contractClassNames = listOf("class1"))
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(jar))
assertThat(whitelist).isEqualTo(mapOf(
"class1" to listOf(jar.hash)
))
}
@Test
fun `single contract jar against single whitelist of different contract`() {
val class1JarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(class1JarHash))
val jar = TestContractsJar(contractClassNames = listOf("class2"))
val whitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
assertThat(whitelist).isEqualTo(mapOf(
"class1" to listOf(class1JarHash),
"class2" to listOf(jar.hash)
))
}
@Test
fun `same jar with single contract`() {
val jarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(jarHash))
val jar = TestContractsJar(hash = jarHash, contractClassNames = listOf("class1"))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
assertThat(newWhitelist).isEqualTo(existingWhitelist)
}
@Test
fun `jar with updated contract`() {
val previousJarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1"))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
assertThat(newWhitelist).isEqualTo(mapOf(
"class1" to listOf(previousJarHash, newContractsJar.hash)
))
}
@Test
fun `jar with one existing contract and one new one`() {
val previousJarHash = SecureHash.randomSHA256()
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
assertThat(newWhitelist).isEqualTo(mapOf(
"class1" to listOf(previousJarHash, newContractsJar.hash),
"class2" to listOf(newContractsJar.hash)
))
}
@Test
fun `two versions of the same contract`() {
val version1Jar = TestContractsJar(contractClassNames = listOf("class1"))
val version2Jar = TestContractsJar(contractClassNames = listOf("class1"))
val newWhitelist = generateWhitelist(emptyMap(), emptyList(), listOf(version1Jar, version2Jar))
assertThat(newWhitelist).isEqualTo(mapOf(
"class1" to listOf(version1Jar.hash, version2Jar.hash)
))
}
@Test
fun `jar with single new contract that's excluded`() {
val jar = TestContractsJar(contractClassNames = listOf("class1"))
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
assertThat(whitelist).isEmpty()
}
@Test
fun `jar with two new contracts, one of which is excluded`() {
val jar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
assertThat(whitelist).isEqualTo(mapOf(
"class2" to listOf(jar.hash)
))
}
@Test
fun `jar with updated contract but it's excluded`() {
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
val jar = TestContractsJar(contractClassNames = listOf("class1"))
assertThatIllegalArgumentException().isThrownBy {
generateWhitelist(existingWhitelist, listOf("class1"), listOf(jar))
}
}
}
private fun generateWhitelist(existingWhitelist: Map<String, List<AttachmentId>>,
excludeContracts: List<ContractClassName>,
contractJars: List<TestContractsJar>): Map<String, List<AttachmentId>> {
return generateWhitelist(
testNetworkParameters(whitelistedContractImplementations = existingWhitelist),
excludeContracts,
contractJars
)
}
}

View File

@ -288,7 +288,7 @@ open class NodeStartup(val args: Array<String>) {
logger.info("Revision: ${versionInfo.revision}")
val info = ManagementFactory.getRuntimeMXBean()
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
logger.info("Main class: ${NodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
logger.info("Main class: ${NodeConfiguration::class.java.location.toURI().path}")
logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
logger.info("Application Args: ${args.joinToString(" ")}")
logger.info("bootclasspath: ${info.bootClassPath}")

View File

@ -315,7 +315,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
private fun findPlugins(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
return ServiceLoader.load(SerializationWhitelist::class.java, URLClassLoader(arrayOf(cordappJarPath.url), appClassLoader)).toList().filter {
it.javaClass.protectionDomain.codeSource.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
it.javaClass.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
}

View File

@ -26,15 +26,13 @@ class AMQPServerSerializationScheme(
throw UnsupportedOperationException()
}
override fun rpcServerSerializerFactory(context: SerializationContext) =
SerializerFactory(
context.whitelist,
context.deserializationClassLoader
).apply {
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
register(RpcServerObservableSerializer())
register(RpcServerCordaFutureSerializer(this))
register(RxNotificationSerializer(this))
}
}
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return canDeserializeVersion(magic) &&

View File

@ -446,7 +446,7 @@ val Class<out FlowLogic<*>>.flowVersionAndInitiatingClass: Pair<Int, Class<out F
val Class<out FlowLogic<*>>.appName: String
get() {
val jarFile = protectionDomain.codeSource.location.toPath()
val jarFile = location.toPath()
return if (jarFile.isRegularFile() && jarFile.toString().endsWith(".jar")) {
jarFile.fileName.toString().removeSuffix(".jar")
} else {

View File

@ -40,7 +40,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
override val objectReferencesEnabled: Boolean,
override val useCase: SerializationContext.UseCase,
override val encoding: SerializationEncoding?,
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext {
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
override val lenientCarpenterEnabled: Boolean = false) : SerializationContext {
private val builder = AttachmentsClassLoaderBuilder(properties, deserializationClassLoader)
/**
@ -62,6 +63,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
return copy(objectReferencesEnabled = false)
}
override fun withLenientCarpenter(): SerializationContext = copy(lenientCarpenterEnabled = true)
override fun withClassLoader(classLoader: ClassLoader): SerializationContext {
return copy(deserializationClassLoader = classLoader)
}

View File

@ -15,6 +15,7 @@ import net.corda.core.serialization.SerializationContext
fun createSerializerFactoryFactory(): SerializerFactoryFactory = SerializerFactoryFactoryImpl()
open class SerializerFactoryFactoryImpl : SerializerFactoryFactory {
override fun make(context: SerializationContext) =
SerializerFactory(context.whitelist, context.deserializationClassLoader)
override fun make(context: SerializationContext): SerializerFactory {
return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled)
}
}

View File

@ -17,6 +17,8 @@ import net.corda.serialization.internal.amqp.SerializerFactory.Companion.nameFor
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Type
import kotlin.reflect.jvm.javaConstructor
@ -39,8 +41,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val typeName = nameForType(clazz)
override val typeDescriptor = Symbol.valueOf(
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")!!
override val typeDescriptor: Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
// We restrict to only those annotated or whitelisted
private val interfaces = interfacesForSerialization(clazz, factory)
@ -129,7 +130,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) {
logger.trace { "Calling setter based construction for ${clazz.typeName}" }
val instance: Any = javaConstructor?.newInstance() ?: throw NotSerializableException(
val instance: Any = javaConstructor?.newInstanceUnwrapped() ?: throw NotSerializableException(
"Failed to instantiate instance of object $clazz")
// read the properties out of the serialised form, since we're invoking the setters the order we
@ -163,7 +164,15 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
+ "serialized properties.")
}
return javaConstructor?.newInstance(*properties.toTypedArray())
return javaConstructor?.newInstanceUnwrapped(*properties.toTypedArray())
?: throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.")
}
private fun <T> Constructor<T>.newInstanceUnwrapped(vararg args: Any?): T {
try {
return newInstance(*args)
} catch (e: InvocationTargetException) {
throw e.cause!!
}
}
}

View File

@ -18,6 +18,7 @@ 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.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
@ -63,40 +64,43 @@ open class SerializerFactory(
private val serializersByType: MutableMap<Type, AMQPSerializer<Any>>,
val serializersByDescriptor: MutableMap<Any, AMQPSerializer<Any>>,
private val customSerializers: MutableList<SerializerFor>,
val transformsCache: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
val transformsCache: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>
) {
@DeleteForDJVM
constructor(whitelist: ClassWhitelist,
classCarpenter: ClassCarpenter,
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
) : this(whitelist, classCarpenter, evolutionSerializerGetter, fingerPrinter,
serializersByType = ConcurrentHashMap(),
serializersByDescriptor = ConcurrentHashMap(),
customSerializers = CopyOnWriteArrayList(),
transformsCache = ConcurrentHashMap())
) : this(
whitelist,
classCarpenter,
evolutionSerializerGetter,
fingerPrinter,
ConcurrentHashMap(),
ConcurrentHashMap(),
CopyOnWriteArrayList(),
ConcurrentHashMap()
)
@DeleteForDJVM
constructor(whitelist: ClassWhitelist,
classLoader: ClassLoader,
lenientCarpenter: Boolean = false,
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
) : this(whitelist, ClassCarpenterImpl(classLoader, whitelist), evolutionSerializerGetter, fingerPrinter,
serializersByType = ConcurrentHashMap(),
serializersByDescriptor = ConcurrentHashMap(),
customSerializers = CopyOnWriteArrayList(),
transformsCache = ConcurrentHashMap())
) : this(whitelist, ClassCarpenterImpl(classLoader, whitelist, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
init {
fingerPrinter.setOwner(this)
}
val classloader: ClassLoader
get() = classCarpenter.classloader
val classloader: ClassLoader get() = classCarpenter.classloader
private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas) = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
private val logger = loggerFor<SerializerFactory>()
private fun getEvolutionSerializer(typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> {
return evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
}
/**
* Look up, and manufacture if necessary, a serializer for the given type.
@ -390,6 +394,8 @@ open class SerializerFactory(
}
companion object {
private val logger = contextLogger()
fun isPrimitive(type: Type): Boolean = primitiveTypeName(type) != null
fun primitiveTypeName(type: Type): String? {
@ -479,4 +485,3 @@ open class SerializerFactory(
override fun toString(): String = "?"
}
}

View File

@ -73,7 +73,7 @@ fun CompositeType.carpenterSchema(classloader: ClassLoader,
}
try {
providesList.add(classloader.loadClass(it))
providesList.add(classloader.loadClass(it.stripGenerics()))
} catch (e: ClassNotFoundException) {
carpenterSchemas.addDepPair(this, name, it)
isCreatable = false

View File

@ -16,6 +16,8 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
@ -112,8 +114,9 @@ interface ClassCarpenter {
* Equals/hashCode methods are not yet supported.
*/
@DeleteForDJVM
class ClassCarpenterImpl(cl: ClassLoader, override val whitelist: ClassWhitelist) : ClassCarpenter {
constructor(whitelist: ClassWhitelist) : this(Thread.currentThread().contextClassLoader, whitelist)
class ClassCarpenterImpl(cl: ClassLoader = Thread.currentThread().contextClassLoader,
override val whitelist: ClassWhitelist,
private val lenient: Boolean = false) : ClassCarpenter {
// TODO: Generics.
// TODO: Sandbox the generated code when a security manager is in use.
@ -449,25 +452,36 @@ class ClassCarpenterImpl(cl: ClassLoader, override val whitelist: ClassWhitelist
// actually called, which is a bit too dynamic for my tastes.
val allFields = schema.fieldsIncludingSuperclasses()
for (itf in schema.interfaces) {
itf.methods.forEach {
val fieldNameFromItf = when {
it.name.startsWith("get") -> it.name.substring(3).decapitalize()
else -> throw InterfaceMismatchNonGetterException(itf, it)
methodLoop@
for (method in itf.methods) {
val fieldNameFromItf = if (method.name.startsWith("get")) {
method.name.substring(3).decapitalize()
} else if (lenient) {
logger.debug { "Ignoring interface $method which is not a getter" }
continue@methodLoop
} else {
throw InterfaceMismatchNonGetterException(itf, method)
}
// If we're trying to carpent a class that prior to serialisation / deserialization
// was made by a carpenter then we can ignore this (it will implement a plain get
// method from SimpleFieldAccess).
if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach
if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) continue@methodLoop
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields)) {
throw InterfaceMismatchMissingAMQPFieldException(itf, fieldNameFromItf)
if (lenient) {
logger.debug { "Ignoring interface $method which is not backed by an AMQP field" }
} else {
throw InterfaceMismatchMissingAMQPFieldException(itf, fieldNameFromItf)
}
}
}
}
}
companion object {
private val logger = contextLogger()
@JvmStatic
@Suppress("UNUSED")
fun getField(obj: Any, name: String): Any? = obj.javaClass.getMethod("get" + name.capitalize()).invoke(obj)

View File

@ -18,6 +18,9 @@ import org.junit.Test;
import java.io.NotSerializableException;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.assertj.core.api.Assertions.*;
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
public class ErrorMessageTests {
private String errMsg(String property, String testname) {
@ -42,19 +45,10 @@ public class ErrorMessageTests {
@Test
public void testJavaConstructorAnnotations() {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializationOutput ser = new SerializationOutput(testDefaultFactory());
SerializationOutput ser = new SerializationOutput(factory1);
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1), TestSerializationContext.testSerializationContext))
assertThatThrownBy(() -> ser.serialize(new C(1), TestSerializationContext.testSerializationContext))
.isInstanceOf(NotSerializableException.class)
.hasMessage(errMsg("a", getClass().getName()));
}
}

View File

@ -1,7 +1,6 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.SerializationCustomSerializer;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Test;
@ -9,6 +8,8 @@ import java.io.NotSerializableException;
import java.util.ArrayList;
import java.util.List;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
public class JavaCustomSerializerTests {
/**
* The class lacks a public constructor that takes parameters it can associate
@ -87,10 +88,8 @@ public class JavaCustomSerializerTests {
}
@Test
public void serializeExample() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
public void serializeExample() throws NotSerializableException {
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
List<Integer> l = new ArrayList<Integer>(2);
@ -102,7 +101,5 @@ public class JavaCustomSerializerTests {
factory.registerExternal(ccs);
ser.serialize(e, TestSerializationContext.testSerializationContext);
}
}

View File

@ -11,11 +11,11 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Test;
import java.io.NotSerializableException;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.jgroups.util.Util.assertEquals;
public class JavaGenericsTest {
@ -37,11 +37,7 @@ public class JavaGenericsTest {
public void basicGeneric() throws NotSerializableException {
A a1 = new A(1);
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
SerializedBytes<?> bytes = ser.serialize(a1, TestSerializationContext.testSerializationContext);
@ -53,13 +49,9 @@ public class JavaGenericsTest {
}
private SerializedBytes<?> forceWildcardSerialize(A<?> a) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
return (new SerializationOutput(factory)).serialize(a, TestSerializationContext.testSerializationContext);
return (new SerializationOutput(factory)).serialize(a, TestSerializationContext.testSerializationContext);
}
private SerializedBytes<?> forceWildcardSerializeFactory(
@ -69,11 +61,7 @@ public class JavaGenericsTest {
}
private A<?> forceWildcardDeserialize(SerializedBytes<?> bytes) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
DeserializationInput des = new DeserializationInput(factory);
return des.deserialize(bytes, A.class, TestSerializationContext.testSerializationContext);
@ -95,11 +83,7 @@ public class JavaGenericsTest {
@Test
public void forceWildcardSharedFactory() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory);
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();

View File

@ -4,28 +4,25 @@ import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.io.NotSerializableException;
import java.util.List;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class OuterClass1 {
protected SerializationOutput ser;
DeserializationInput desExisting;
DeserializationInput desRegen;
OuterClass1() {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory1 = testDefaultFactory();
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory2 = testDefaultFactory();
this.ser = new SerializationOutput(factory1);
this.desExisting = new DeserializationInput(factory1);
@ -59,13 +56,9 @@ class OuterClass2 {
DeserializationInput desRegen;
OuterClass2() {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory1 = testDefaultFactory();
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory2 = testDefaultFactory();
this.ser = new SerializationOutput(factory1);
this.desExisting = new DeserializationInput(factory1);
@ -104,9 +97,7 @@ abstract class AbstractClass2 {
protected SerializationOutput ser;
AbstractClass2() {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
this.ser = new SerializationOutput(factory);
}
@ -134,9 +125,7 @@ abstract class AbstractClass3 {
class Inherator5 extends AbstractClass3 {
public void run() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext);
@ -154,9 +143,7 @@ class Inherator6 extends AbstractClass3 {
}
public void run() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
ser.serialize(new Wrapper(new DummyState()), TestSerializationContext.testSerializationContext);
@ -166,57 +153,57 @@ class Inherator6 extends AbstractClass3 {
public class JavaNestedClassesTests {
@Test
public void publicNested() {
Assertions.assertThatThrownBy(() -> new OuterClass1().run()).isInstanceOf(
assertThatThrownBy(() -> new OuterClass1().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void privateNested() {
Assertions.assertThatThrownBy(() -> new OuterClass2().run()).isInstanceOf(
assertThatThrownBy(() -> new OuterClass2().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void publicNestedInherited() {
Assertions.assertThatThrownBy(() -> new Inherator1().run()).isInstanceOf(
assertThatThrownBy(() -> new Inherator1().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
Assertions.assertThatThrownBy(() -> new Inherator1().iRun()).isInstanceOf(
assertThatThrownBy(() -> new Inherator1().iRun()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void protectedNestedInherited() {
Assertions.assertThatThrownBy(() -> new Inherator2().run()).isInstanceOf(
assertThatThrownBy(() -> new Inherator2().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
Assertions.assertThatThrownBy(() -> new Inherator2().iRun()).isInstanceOf(
assertThatThrownBy(() -> new Inherator2().iRun()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void abstractNested() {
Assertions.assertThatThrownBy(() -> new Inherator4().run()).isInstanceOf(
assertThatThrownBy(() -> new Inherator4().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void abstractNestedFactoryOnNested() {
Assertions.assertThatThrownBy(() -> new Inherator5().run()).isInstanceOf(
assertThatThrownBy(() -> new Inherator5().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void abstractNestedFactoryOnNestedInWrapper() {
Assertions.assertThatThrownBy(() -> new Inherator6().run()).isInstanceOf(
assertThatThrownBy(() -> new Inherator6().run()).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}

View File

@ -3,15 +3,16 @@ package net.corda.serialization.internal.amqp;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.io.NotSerializableException;
import java.util.List;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
abstract class JavaNestedInheritenceTestsBase {
class DummyState implements ContractState {
@Override @NotNull public List<AbstractParty> getParticipants() {
@ -33,38 +34,32 @@ class TemplateWrapper<T> {
public class JavaNestedInheritenceTests extends JavaNestedInheritenceTestsBase {
@Test
public void serializeIt() {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
Assertions.assertThatThrownBy(() -> ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext)).isInstanceOf(
assertThatThrownBy(() -> ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void serializeIt2() {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
Assertions.assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}
@Test
public void serializeIt3() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
public void serializeIt3() {
SerializerFactory factory1 = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory1);
Assertions.assertThatThrownBy(() -> ser.serialize(new TemplateWrapper<ContractState> (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
assertThatThrownBy(() -> ser.serialize(new TemplateWrapper<ContractState> (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class).hasMessageContaining(
"has synthetic fields and is likely a nested inner class");
}

View File

@ -10,15 +10,16 @@
package net.corda.serialization.internal.amqp;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.NotSerializableException;
import java.lang.reflect.Field;
import java.util.Map;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.junit.Assert.*;
public class JavaPrivatePropertyTests {
static class C {
private String a;
@ -86,10 +87,8 @@ public class JavaPrivatePropertyTests {
}
@Test
public void singlePrivateBooleanWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
public void singlePrivateBooleanWithConstructor() throws NotSerializableException {
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
@ -99,10 +98,8 @@ public class JavaPrivatePropertyTests {
}
@Test
public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException {
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
@ -114,10 +111,8 @@ public class JavaPrivatePropertyTests {
}
@Test
public void testCapitilsationOfIs() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
public void testCapitilsationOfIs() throws NotSerializableException {
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
@ -130,10 +125,8 @@ public class JavaPrivatePropertyTests {
}
@Test
public void singlePrivateIntWithBoolean() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
public void singlePrivateIntWithBoolean() throws NotSerializableException {
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
@ -146,9 +139,7 @@ public class JavaPrivatePropertyTests {
@Test
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
@ -176,10 +167,7 @@ public class JavaPrivatePropertyTests {
@Test
public void singlePrivateWithConstructorAndGetter()
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializerFactory factory = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);

View File

@ -10,14 +10,14 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Test;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.core.serialization.SerializedBytes;
import java.io.NotSerializableException;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
public class JavaSerialiseEnumTests {
public enum Bras {
@ -40,10 +40,7 @@ public class JavaSerialiseEnumTests {
public void testJavaConstructorAnnotations() throws NotSerializableException {
Bra bra = new Bra(Bras.UNDERWIRE);
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter(),
new SerializerFingerPrinter());
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(bra, TestSerializationContext.testSerializationContext);
SerializationOutput ser = new SerializationOutput(testDefaultFactory());
ser.serialize(bra, TestSerializationContext.testSerializationContext);
}
}

View File

@ -11,7 +11,6 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.ConstructorForDeserialization;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.apache.qpid.proton.codec.DecoderImpl;
@ -23,6 +22,7 @@ import java.io.NotSerializableException;
import java.nio.ByteBuffer;
import java.util.Objects;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.junit.Assert.assertTrue;
public class JavaSerializationOutputTests {
@ -186,14 +186,8 @@ public class JavaSerializationOutputTests {
}
private Object serdes(Object obj) throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
SerializerFactory factory2 = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(obj, TestSerializationContext.testSerializationContext);

View File

@ -12,7 +12,6 @@ package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.CordaSerializable;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Assert;
import org.junit.Test;
@ -20,6 +19,8 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
public class ListsSerializationJavaTest {
@CordaSerializable
@ -136,12 +137,7 @@ public class ListsSerializationJavaTest {
// Have to have own version as Kotlin inline functions cannot be easily called from Java
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerializerGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(container, TestSerializationContext.testSerializationContext);
DeserializationInput des = new DeserializationInput(factory1);

View File

@ -11,16 +11,17 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.AllWhitelist;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.NotSerializableException;
import java.util.ArrayList;
import java.util.List;
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
public class SetterConstructorTests {
static class C {
@ -129,13 +130,7 @@ public class SetterConstructorTests {
// despite having no constructor we should still be able to serialise an instance of C
@Test
public void serialiseC() throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
SerializationOutput ser = new SerializationOutput(factory1);
@ -205,13 +200,7 @@ public class SetterConstructorTests {
@Test
public void deserialiseC() throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
C cPre1 = new C();
@ -278,13 +267,7 @@ public class SetterConstructorTests {
@Test
public void serialiseOuterAndInner() throws NotSerializableException {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
Inner1 i1 = new Inner1("Hello");
Inner2 i2 = new Inner2();
@ -308,38 +291,26 @@ public class SetterConstructorTests {
@Test
public void typeMistmatch() {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
TypeMismatch tm = new TypeMismatch();
tm.setA(10);
assertEquals("10", tm.getA());
Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
TestSerializationContext.testSerializationContext)).isInstanceOf (
NotSerializableException.class);
}
@Test
public void typeMistmatch2() {
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
TypeMismatch2 tm = new TypeMismatch2();
tm.setA("10");
assertEquals((Integer)10, tm.getA());
Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
TestSerializationContext.testSerializationContext)).isInstanceOf(
NotSerializableException.class);
}
@ -356,13 +327,7 @@ public class SetterConstructorTests {
cil.setL(l);
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter,
fingerPrinter);
SerializerFactory factory1 = testDefaultFactory();
// if we've got super / sub types on the setter vs the underlying type the wrong way around this will
// explode. See CORDA-1229 (https://r3-cev.atlassian.net/browse/CORDA-1229)

View File

@ -11,11 +11,8 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.serialization.internal.carpenter.*
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import org.junit.Test
import kotlin.test.*

View File

@ -11,15 +11,13 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.CordaSerializable
import net.corda.serialization.internal.carpenter.*
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryWithWhitelist
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.serialization.internal.carpenter.*
import org.junit.Test
import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@CordaSerializable
interface I {

View File

@ -56,8 +56,8 @@ class FingerPrinterTestingTests {
val factory = SerializerFactory(
AllWhitelist,
ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting(),
FingerPrinterTesting())
evolutionSerializerGetter = EvolutionSerializerGetterTesting(),
fingerPrinter = FingerPrinterTesting())
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))

View File

@ -17,13 +17,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.rpc.RPCException
import net.corda.core.CordaException
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes
@ -32,48 +26,25 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.x500Name
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.EncodingWhitelist
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.*
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.CordaSerializationEncoding
import net.corda.serialization.internal.EmptyWhitelist
import net.corda.serialization.internal.GeneratedAttachment
import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.isPrimitive
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testSerializationContext
import net.corda.serialization.internal.encodingNotPermittedFormat
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.qpid.proton.amqp.Decimal128
import org.apache.qpid.proton.amqp.Decimal32
import org.apache.qpid.proton.amqp.Decimal64
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.amqp.UnsignedByte
import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.amqp.UnsignedLong
import org.apache.qpid.proton.amqp.UnsignedShort
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Assertions.catchThrowable
import org.assertj.core.api.Assertions.*
import org.bouncycastle.cert.X509v2CRLBuilder
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
@ -89,20 +60,7 @@ import java.io.NotSerializableException
import java.math.BigDecimal
import java.math.BigInteger
import java.security.cert.X509CRL
import java.time.DayOfWeek
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.Month
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZonedDateTime
import java.time.*
import java.time.temporal.ChronoUnit
import java.util.*
import kotlin.reflect.full.superclasses
@ -259,9 +217,13 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression)
}
private fun defaultFactory() = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting())
private fun defaultFactory(): SerializerFactory {
return SerializerFactory(
AllWhitelist,
ClassLoader.getSystemClassLoader(),
evolutionSerializerGetter = EvolutionSerializerGetterTesting()
)
}
private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = defaultFactory(),

View File

@ -1,16 +1,25 @@
package net.corda.serialization.internal.amqp.testutils
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.internal.packageName
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import org.apache.qpid.proton.codec.Data
import net.corda.core.utilities.OpaqueBytes
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.EmptyWhitelist
import net.corda.serialization.internal.amqp.*
import net.corda.testing.common.internal.ProjectStructure
import org.apache.qpid.proton.codec.Data
import org.junit.Test
import java.io.File.separatorChar
import java.io.NotSerializableException
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting())
fun testDefaultFactoryNoEvolution(): SerializerFactory {
return SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(), evolutionSerializerGetter = EvolutionSerializerGetterTesting())
}
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
class TestSerializationOutput(
@ -41,8 +50,25 @@ class TestSerializationOutput(
}
}
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
fun testName(): String {
val classLoader = Thread.currentThread().contextClassLoader
return Thread.currentThread().stackTrace.first {
try {
classLoader.loadClass(it.className).getMethod(it.methodName).isAnnotationPresent(Test::class.java)
} catch (e: Exception) {
false
}
}.methodName
}
fun Any.testResourceName(): String = "${javaClass.simpleName}.${testName()}"
fun Any.writeTestResource(bytes: OpaqueBytes) {
val dir = ProjectStructure.projectRootDir / "serialization" / "src" / "test" / "resources" / javaClass.packageName.replace('.', separatorChar)
bytes.open().copyTo(dir / testResourceName(), REPLACE_EXISTING)
}
fun Any.readTestResource(): ByteArray = javaClass.getResourceAsStream(testResourceName()).readBytes()
@Throws(NotSerializableException::class)
inline fun <reified T : Any> DeserializationInput.deserializeAndReturnEnvelope(

View File

@ -12,6 +12,7 @@ package net.corda.serialization.internal.carpenter
import net.corda.core.internal.uncheckedCast
import net.corda.serialization.internal.AllWhitelist
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test
import java.beans.Introspector
import java.lang.reflect.Field
@ -155,21 +156,40 @@ class ClassCarpenterTest {
assertEquals(1, i.b)
}
@Test(expected = InterfaceMismatchException::class)
fun `mismatched interface`() {
val schema1 = ClassSchema(
@Test
fun `unimplemented interface method with lenient = false`() {
val schemaA = ClassSchema(
"gen.A",
mapOf("a" to NonNullableField(String::class.java)))
val schema2 = ClassSchema(
val schemaB = ClassSchema(
"gen.B",
mapOf("c" to NonNullableField(Int::class.java)),
schema1,
schemaA,
interfaces = listOf(DummyInterface::class.java))
val clazz = cc.build(schema2)
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
assertEquals(1, i.b)
assertThatExceptionOfType(InterfaceMismatchException::class.java).isThrownBy { cc.build(schemaB) }
}
@Test
fun `unimplemented interface method with lenient = true`() {
val cc = ClassCarpenterImpl(whitelist = AllWhitelist, lenient = true)
val schemaA = ClassSchema(
"gen.A",
mapOf("a" to NonNullableField(String::class.java)))
val schemaB = ClassSchema(
"gen.B",
mapOf("c" to NonNullableField(Int::class.java)),
schemaA,
interfaces = listOf(DummyInterface::class.java))
val classB = cc.build(schemaB)
val b = classB.constructors[0].newInstance("xa", 1) as DummyInterface
assertEquals("xa", b.a)
assertEquals(1, classB.getMethod("getC").invoke(b))
assertThatExceptionOfType(AbstractMethodError::class.java).isThrownBy { b.b }
}
@Test

View File

@ -15,6 +15,7 @@ import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.Field
import net.corda.serialization.internal.amqp.Schema
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testName
fun mangleName(name: String) = "${name}__carpenter"
@ -57,7 +58,6 @@ open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
var factory = SerializerFactoryExternalCarpenter(cc)
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
@Suppress("NOTHING_TO_INLINE")
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
}

View File

@ -0,0 +1,49 @@
package net.corda.serialization.internal.carpenter
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.deserialize
import net.corda.serialization.internal.amqp.testutils.readTestResource
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Rule
import org.junit.Test
class SerDeserCarpentryTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun implementingGenericInterface() {
// Original class that was serialised
// data class GenericData(val a: Int) : GenericInterface<String>
// writeTestResource(GenericData(123).serialize())
val data = readTestResource().deserialize<GenericInterface<*>>()
assertThat(data.javaClass.getMethod("getA").invoke(data)).isEqualTo(123)
}
@Test
fun lenientCarpenter() {
// Original class that was serialised
// data class Data(val b: Int) : AInterface {
// override val a: Int get() = b
// }
// writeTestResource(Data(123).serialize())
val data = readTestResource().deserialize<AInterface>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
assertThat(data.javaClass.getMethod("getB").invoke(data)).isEqualTo(123)
assertThatExceptionOfType(AbstractMethodError::class.java).isThrownBy { data.a }
}
@Suppress("unused")
@CordaSerializable
interface GenericInterface<T>
@CordaSerializable
interface AInterface {
val a: Int
}
}

View File

@ -5,7 +5,7 @@ apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':client:jackson')
compile 'info.picocli:picocli:3.0.0'
compile "info.picocli:picocli:$picocli_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"

View File

@ -7,6 +7,7 @@ import net.corda.client.jackson.JacksonSupport
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.rootMessage
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv
@ -33,7 +34,7 @@ fun main(args: Array<String>) {
if (main.verbose) {
throwable.printStackTrace()
} else {
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
System.err.println("*ERROR*: ${throwable.rootMessage}. Use --verbose for more details")
}
exitProcess(1)
}
@ -41,7 +42,7 @@ fun main(args: Array<String>) {
@Command(
name = "Blob Inspector",
versionProvider = VersionProvider::class,
versionProvider = CordaVersionProvider::class,
mixinStandardHelpOptions = true, // add --help and --version options,
showDefaultValues = true,
description = ["Inspect AMQP serialised binary blobs"]
@ -64,7 +65,9 @@ class Main : Runnable {
var verbose: Boolean = false
override fun run() {
System.setProperty("logLevel", if (verbose) "trace" else "off")
if (verbose) {
System.setProperty("logLevel", "trace")
}
val bytes = source!!.readBytes().run {
require(size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
@ -89,7 +92,8 @@ class Main : Runnable {
}
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
val deserialized = bytes.deserialize<Any>()
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
val deserialized = bytes.deserialize<Any>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
println(deserialized.javaClass.name)
mapper.writeValue(System.out, deserialized)
}
@ -124,8 +128,13 @@ private class SourceConverter : ITypeConverter<URL> {
}
}
private class VersionProvider : IVersionProvider {
override fun getVersion(): Array<String> = arrayOf(Manifests.read("Corda-Release-Version"))
private class CordaVersionProvider : IVersionProvider {
override fun getVersion(): Array<String> {
return arrayOf(
"Version: ${Manifests.read("Corda-Release-Version")}",
"Revision: ${Manifests.read("Corda-Revision")}"
)
}
}
private enum class FormatType { YAML, JSON }

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<Property name="logLevel">off</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern="[%C{1}.%M] %m%n"/>

View File

@ -8,54 +8,39 @@
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'Network bootstrapper'
configurations {
runtimeArtifacts
dependencies {
compile project(':node-api')
compile "info.picocli:picocli:$picocli_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
}
jar {
baseName "corda-tools-network-bootstrapper"
}
dependencies {
compile "org.slf4j:slf4j-nop:$slf4j_version"
}
task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').compileJava) {
applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper'
archiveName "tools-network-bootstrapper-${corda_release_version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'Network Bootstrapper'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
from(project(':node:capsule').tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
applicationSource = files(
project(':node-api').configurations.runtime,
project(':node-api').jar
)
}
artifacts {
runtimeArtifacts buildBootstrapperJar
publish buildBootstrapperJar {
classifier ""
archiveName = "network-bootstrapper-${corda_release_version}.jar"
manifest {
attributes(
'Automatic-Module-Name': 'net.corda.bootstrapper',
'Main-Class': 'net.corda.bootstrapper.MainKt'
)
}
}
jar {
classifier "ignore"
}
publish {
disableDefaultJar = true
name 'corda-tools-network-bootstrapper'
}

View File

@ -0,0 +1,65 @@
package net.corda.bootstrapper
import com.jcabi.manifests.Manifests
import net.corda.core.internal.rootMessage
import net.corda.nodeapi.internal.network.NetworkBootstrapper
import picocli.CommandLine
import picocli.CommandLine.*
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.system.exitProcess
fun main(args: Array<String>) {
val main = Main()
try {
CommandLine.run(main, *args)
} catch (e: ExecutionException) {
val throwable = e.cause ?: e
if (main.verbose) {
throwable.printStackTrace()
} else {
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
}
exitProcess(1)
}
}
@Command(
name = "Network Bootstrapper",
versionProvider = CordaVersionProvider::class,
mixinStandardHelpOptions = true,
showDefaultValues = true,
description = [ "Bootstrap a local test Corda network using a set of node conf files and CorDapp JARs" ]
)
class Main : Runnable {
@Option(
names = ["--dir"],
description = [
"Root directory containing the node conf files and CorDapp JARs that will form the test network.",
"It may also contain existing node directories."
]
)
private var dir: Path = Paths.get(".")
@Option(names = ["--no-copy"], description = ["""Don't copy the CorDapp JARs into the nodes' "cordapps" directories."""])
private var noCopy: Boolean = false
@Option(names = ["--verbose"], description = ["Enable verbose output."])
var verbose: Boolean = false
override fun run() {
if (verbose) {
System.setProperty("logLevel", "trace")
}
NetworkBootstrapper().bootstrap(dir.toAbsolutePath().normalize(), copyCordapps = !noCopy)
}
}
private class CordaVersionProvider : IVersionProvider {
override fun getVersion(): Array<String> {
return arrayOf(
"Version: ${Manifests.read("Corda-Release-Version")}",
"Revision: ${Manifests.read("Corda-Revision")}"
)
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<Property name="logLevel">off</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern="[%C{1}.%M] %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="${sys:logLevel}">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>

View File

@ -14,6 +14,7 @@ package net.corda.webserver
import com.typesafe.config.ConfigException
import net.corda.core.internal.div
import net.corda.core.internal.location
import net.corda.core.internal.rootCause
import net.corda.webserver.internal.NodeWebServer
import org.slf4j.LoggerFactory
@ -58,7 +59,7 @@ fun main(args: Array<String>) {
exitProcess(2)
}
log.info("Main class: ${WebServerConfig::class.java.protectionDomain.codeSource.location.toURI().path}")
log.info("Main class: ${WebServerConfig::class.java.location.toURI().path}")
val info = ManagementFactory.getRuntimeMXBean()
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
log.info("Application Args: ${args.joinToString(" ")}")