mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
ENT-11065: Remove the need for JVM flags in client code (#7635)
This commit is contained in:
parent
406f7ff292
commit
2e63ca6264
39
build.gradle
39
build.gradle
@ -121,23 +121,6 @@ buildscript {
|
||||
ext.fontawesomefx_commons_version = constants.getProperty("fontawesomefxCommonsVersion")
|
||||
ext.fontawesomefx_fontawesome_version = constants.getProperty("fontawesomefxFontawesomeVersion")
|
||||
ext.javaassist_version = constants.getProperty("javaassistVersion")
|
||||
ext.test_add_opens = [
|
||||
'--add-opens', 'java.base/java.time=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.io=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.util=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.net=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.nio=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.lang.invoke=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.security.cert=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.security=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/javax.net.ssl=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED',
|
||||
'--add-opens', 'java.sql/java.sql=ALL-UNNAMED'
|
||||
]
|
||||
ext.test_add_exports = [
|
||||
'--add-exports', 'java.base/sun.nio.ch=ALL-UNNAMED'
|
||||
]
|
||||
|
||||
ext.corda_revision = {
|
||||
try {
|
||||
@ -282,11 +265,6 @@ allprojects {
|
||||
toolVersion = "0.8.7"
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
@ -332,13 +310,12 @@ allprojects {
|
||||
}
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
jvmArgs += project(":node:capsule").file("src/main/resources/node-jvm-args.txt").readLines()
|
||||
jvmArgs += "--add-modules=jdk.incubator.foreign" // For the SharedMemoryIncremental
|
||||
forkEvery = 20
|
||||
ignoreFailures = project.hasProperty('tests.ignoreFailures') ? project.property('tests.ignoreFailures').toBoolean() : false
|
||||
failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : false
|
||||
|
||||
// Prevent the project from creating temporary files outside of the build directory.
|
||||
systemProperty 'java.io.tmpdir', buildDir.absolutePath
|
||||
|
||||
maxHeapSize = "1g"
|
||||
|
||||
if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) {
|
||||
@ -351,15 +328,15 @@ allprojects {
|
||||
// ex.append = false
|
||||
}
|
||||
|
||||
maxParallelForks = (System.env.CORDA_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_TESTING_FORKS".toInteger()
|
||||
|
||||
systemProperty 'java.security.egd', 'file:/dev/./urandom'
|
||||
}
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
if (name.contains("integrationTest")) {
|
||||
maxParallelForks = (System.env.CORDA_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_INT_TESTING_FORKS".toInteger()
|
||||
} else {
|
||||
maxParallelForks = (System.env.CORDA_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_TESTING_FORKS".toInteger()
|
||||
}
|
||||
|
||||
// Prevent the project from creating temporary files outside of the build directory.
|
||||
systemProperty 'java.io.tmpdir', buildDir.absolutePath
|
||||
systemProperty 'java.security.egd', 'file:/dev/./urandom'
|
||||
}
|
||||
|
||||
group 'net.corda'
|
||||
|
@ -10,6 +10,9 @@ description 'Corda client RPC modules'
|
||||
configurations {
|
||||
integrationTestImplementation.extendsFrom testImplementation
|
||||
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||
|
||||
smokeTestImplementation.extendsFrom compile
|
||||
smokeTestRuntimeOnly.extendsFrom runtimeOnly
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -28,55 +31,95 @@ sourceSets {
|
||||
srcDirs "src/integration-test/resources"
|
||||
}
|
||||
}
|
||||
smokeTest {
|
||||
kotlin {
|
||||
// We must NOT have any Node code on the classpath, so do NOT
|
||||
// include the test or integrationTest dependencies here.
|
||||
compileClasspath += main.output
|
||||
runtimeClasspath += main.output
|
||||
srcDir file('src/smoke-test/kotlin')
|
||||
}
|
||||
java {
|
||||
compileClasspath += main.output
|
||||
runtimeClasspath += main.output
|
||||
srcDir file('src/smoke-test/java')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
implementation project(':serialization')
|
||||
|
||||
// For caches rather than guava
|
||||
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
|
||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
|
||||
// Unit testing helpers.
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
testImplementation "org.assertj:assertj-core:${assertj_version}"
|
||||
testImplementation "io.dropwizard.metrics:metrics-core:$metrics_version"
|
||||
implementation "io.reactivex:rxjava:$rxjava_version"
|
||||
implementation("org.apache.activemq:artemis-core-client:${artemis_version}") {
|
||||
exclude group: 'org.jgroups', module: 'jgroups'
|
||||
}
|
||||
implementation "com.google.guava:guava-testlib:$guava_version"
|
||||
|
||||
testImplementation project(':node')
|
||||
testImplementation project(':node-driver')
|
||||
testImplementation project(':client:mock')
|
||||
testImplementation project(':core-test-utils')
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
// Unit testing helpers.
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
testImplementation "org.assertj:assertj-core:${assertj_version}"
|
||||
testImplementation "io.dropwizard.metrics:metrics-core:$metrics_version"
|
||||
|
||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
|
||||
integrationTestImplementation project(path: ':node-api', configuration: 'testArtifacts')
|
||||
integrationTestImplementation project(':common-configuration-parsing')
|
||||
integrationTestImplementation project(':finance:contracts')
|
||||
integrationTestImplementation project(':finance:workflows')
|
||||
integrationTestImplementation project(':test-utils')
|
||||
|
||||
integrationTestImplementation "co.paralleluniverse:quasar-core:$quasar_version"
|
||||
integrationTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
|
||||
|
||||
implementation "io.reactivex:rxjava:$rxjava_version"
|
||||
implementation("org.apache.activemq:artemis-core-client:${artemis_version}") {
|
||||
exclude group: 'org.jgroups', module: 'jgroups'
|
||||
}
|
||||
implementation "com.google.guava:guava-testlib:$guava_version"
|
||||
smokeTestImplementation project(':core')
|
||||
smokeTestImplementation project(':node-api')
|
||||
smokeTestImplementation project(':finance:contracts')
|
||||
smokeTestImplementation project(':finance:workflows')
|
||||
smokeTestImplementation project(':smoke-test-utils')
|
||||
smokeTestImplementation project(':testing:cordapps:sleeping')
|
||||
smokeTestImplementation "junit:junit:$junit_version"
|
||||
smokeTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
smokeTestImplementation "com.google.guava:guava:$guava_version"
|
||||
smokeTestImplementation "org.hamcrest:hamcrest-library:2.1"
|
||||
|
||||
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
processSmokeTestResources {
|
||||
// Bring in the fully built corda.jar for use by NodeFactory in the smoke tests
|
||||
from(project(":node:capsule").tasks['buildCordaJAR']) {
|
||||
rename 'corda-(.*)', 'corda.jar'
|
||||
}
|
||||
from(project(':finance:workflows').tasks['jar']) {
|
||||
rename '.*finance-workflows-.*', 'cordapp-finance-workflows.jar'
|
||||
}
|
||||
from(project(':finance:contracts').tasks['jar']) {
|
||||
rename '.*finance-contracts-.*', 'cordapp-finance-contracts.jar'
|
||||
}
|
||||
from(project(':testing:cordapps:sleeping').tasks['jar']) {
|
||||
rename 'testing-sleeping-cordapp-*', 'cordapp-sleeping.jar'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('integrationTest', Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
tasks.register('smokeTest', Test) {
|
||||
testClassesDirs = sourceSets.smokeTest.output.classesDirs
|
||||
classpath = sourceSets.smokeTest.runtimeClasspath
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -54,7 +54,7 @@ import org.junit.Test
|
||||
import rx.subjects.PublishSubject
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.Currency
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
@ -255,21 +255,12 @@ class CordaRPCClientTest : NodeBasedTest(FINANCE_CORDAPPS, notaries = listOf(DUM
|
||||
fun `additional class loader used by WireTransaction when it deserialises its components`() {
|
||||
val financeLocation = Cash::class.java.location.toPath().toString()
|
||||
val classPathWithoutFinance = ProcessUtilities.defaultClassPath.filter { financeLocation !in it }
|
||||
val moduleOpens = listOf(
|
||||
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
|
||||
)
|
||||
|
||||
// Create a Cash.State object for the StandaloneCashRpcClient to get
|
||||
node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
|
||||
val outOfProcessRpc = ProcessUtilities.startJavaProcess<StandaloneCashRpcClient>(
|
||||
classPath = classPathWithoutFinance,
|
||||
arguments = listOf(node.node.configuration.rpcOptions.address.toString(), financeLocation),
|
||||
extraJvmArguments = moduleOpens
|
||||
arguments = listOf(node.node.configuration.rpcOptions.address.toString(), financeLocation)
|
||||
)
|
||||
assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown
|
||||
}
|
||||
|
@ -89,7 +89,6 @@ class RPCStabilityTests {
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
@Ignore("TODO JDK17:Fixme")
|
||||
fun `client and server dont leak threads`() {
|
||||
fun startAndStop() {
|
||||
rpcDriver {
|
||||
@ -122,7 +121,6 @@ class RPCStabilityTests {
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
@Ignore("TODO JDK17:Fixme")
|
||||
fun `client doesnt leak threads when it fails to start`() {
|
||||
fun startAndStop() {
|
||||
rpcDriver {
|
||||
@ -491,7 +489,6 @@ class RPCStabilityTests {
|
||||
* In this test we create a number of out of process RPC clients that call [TrackSubscriberOps.subscribe] in a loop.
|
||||
*/
|
||||
@Test(timeout=300_000)
|
||||
@Ignore("TODO JDK17:Fixme")
|
||||
fun `server cleans up queues after disconnected clients`() {
|
||||
rpcDriver {
|
||||
val trackSubscriberOpsImpl = object : TrackSubscriberOps {
|
||||
|
@ -40,7 +40,6 @@ import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.sleeping.SleepingFlow
|
||||
import net.corda.smoketesting.NodeConfig
|
||||
import net.corda.smoketesting.NodeProcess
|
||||
import org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM
|
||||
import org.hamcrest.text.MatchesPattern
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -50,6 +49,7 @@ import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import java.io.FilterInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream.nullOutputStream
|
||||
import java.util.Currency
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
@ -67,7 +67,7 @@ class StandaloneCordaRPClientTest {
|
||||
val rpcUser = User("rpcUser", "test", permissions = setOf("InvokeRpc.startFlow", "InvokeRpc.killFlow"))
|
||||
val flowUser = User("flowUser", "test", permissions = setOf("StartFlow.net.corda.finance.flows.CashIssueFlow"))
|
||||
val port = AtomicInteger(15200)
|
||||
const val attachmentSize = 2116
|
||||
const val ATTACHMENT_SIZE = 2116
|
||||
val timeout = 60.seconds
|
||||
}
|
||||
|
||||
@ -111,13 +111,13 @@ class StandaloneCordaRPClientTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test attachments`() {
|
||||
val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1)
|
||||
val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1)
|
||||
assertFalse(rpcProxy.attachmentExists(attachment.sha256))
|
||||
val id = attachment.inputStream.use { rpcProxy.uploadAttachment(it) }
|
||||
assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash")
|
||||
|
||||
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it ->
|
||||
it.copyTo(NULL_OUTPUT_STREAM)
|
||||
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use {
|
||||
it.copyTo(nullOutputStream())
|
||||
SecureHash.createSHA256(it.hash().asBytes())
|
||||
}
|
||||
assertEquals(attachment.sha256, hash)
|
||||
@ -126,13 +126,13 @@ class StandaloneCordaRPClientTest {
|
||||
@Ignore("CORDA-1520 - After switching from Kryo to AMQP this test won't work")
|
||||
@Test(timeout=300_000)
|
||||
fun `test wrapped attachments`() {
|
||||
val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1)
|
||||
val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1)
|
||||
assertFalse(rpcProxy.attachmentExists(attachment.sha256))
|
||||
val id = WrapperStream(attachment.inputStream).use { rpcProxy.uploadAttachment(it) }
|
||||
assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash")
|
||||
|
||||
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it ->
|
||||
it.copyTo(NULL_OUTPUT_STREAM)
|
||||
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use {
|
||||
it.copyTo(nullOutputStream())
|
||||
SecureHash.createSHA256(it.hash().asBytes())
|
||||
}
|
||||
assertEquals(attachment.sha256, hash)
|
@ -107,6 +107,7 @@ dependencies {
|
||||
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
smokeTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
smokeTestRuntimeOnly "org.slf4j:slf4j-simple:$slf4j_version"
|
||||
|
||||
smokeTestCompile project(':smoke-test-utils')
|
||||
smokeTestCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
@ -143,9 +144,6 @@ task smokeTest(type: Test) {
|
||||
dependsOn smokeTestJar
|
||||
testClassesDirs = sourceSets.smokeTest.output.classesDirs
|
||||
classpath = sourceSets.smokeTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
idea {
|
||||
|
@ -48,7 +48,7 @@ class NodeVersioningTest {
|
||||
users = listOf(superUser)
|
||||
)
|
||||
|
||||
private lateinit var notary: NodeProcess
|
||||
private var notary: NodeProcess? = null
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@ -57,7 +57,7 @@ class NodeVersioningTest {
|
||||
|
||||
@After
|
||||
fun done() {
|
||||
notary.close()
|
||||
notary?.close()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
|
@ -23,16 +23,18 @@ configurations {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// These are exposed in our public APIs and are thus "api" dependencies
|
||||
api "org.slf4j:slf4j-api:$slf4j_version"
|
||||
// RxJava: observable streams of events.
|
||||
api "io.reactivex:rxjava:$rxjava_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
// SLF4J: commons-logging bindings for a SLF4J back end
|
||||
implementation "org.slf4j:jcl-over-slf4j:$slf4j_version"
|
||||
implementation "org.slf4j:slf4j-api:$slf4j_version"
|
||||
// Guava: Google utilities library.
|
||||
implementation "com.google.guava:guava:$guava_version"
|
||||
// For caches rather than guava
|
||||
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
|
||||
// RxJava: observable streams of events.
|
||||
implementation "io.reactivex:rxjava:$rxjava_version"
|
||||
implementation "org.apache.commons:commons-lang3:$commons_lang3_version"
|
||||
// Java ed25519 implementation. See https://github.com/str4d/ed25519-java/
|
||||
implementation "net.i2p.crypto:eddsa:$eddsa_version"
|
||||
@ -80,10 +82,6 @@ jar {
|
||||
finalizedBy(copyQuasarJar)
|
||||
archiveBaseName = 'corda-core'
|
||||
archiveClassifier = ''
|
||||
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.base/java.net java.base/java.nio')
|
||||
}
|
||||
}
|
||||
|
||||
processTestResources {
|
||||
@ -103,6 +101,7 @@ compileTestJava {
|
||||
}
|
||||
|
||||
test {
|
||||
// TODO This obscures whether any Corda client APIs need these JVM flags as well (which they shouldn't do)
|
||||
jvmArgs += [
|
||||
'--add-exports', 'java.base/sun.security.util=ALL-UNNAMED',
|
||||
'--add-exports', 'java.base/sun.security.x509=ALL-UNNAMED'
|
||||
|
@ -166,7 +166,7 @@ fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.c
|
||||
fun InputStream.readFully(): ByteArray = use { it.readBytes() }
|
||||
|
||||
/** Calculate the hash of the remaining bytes in this input stream. The stream is closed at the end. */
|
||||
fun InputStream.hash(): SecureHash {
|
||||
fun InputStream.hash(): SecureHash.SHA256 {
|
||||
return use {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
@ -309,6 +309,8 @@ inline fun <T, R : Any> Stream<T>.mapNotNull(crossinline transform: (T) -> R?):
|
||||
/** Similar to [Collectors.toSet] except the Set is guaranteed to be ordered. */
|
||||
fun <T> Stream<T>.toSet(): Set<T> = collect(toCollection { LinkedHashSet<T>() })
|
||||
|
||||
val Class<*>.isJdkClass: Boolean get() = module.name?.startsWith("java.") == true
|
||||
|
||||
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
|
||||
|
||||
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */
|
||||
|
@ -88,7 +88,7 @@ inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption =
|
||||
inline fun <reified T : Any> Path.readObject(): T = readBytes().deserialize()
|
||||
|
||||
/** Calculate the hash of the contents of this file. */
|
||||
inline val Path.hash: SecureHash get() = read { it.hash() }
|
||||
inline val Path.hash: SecureHash.SHA256 get() = read { it.hash() }
|
||||
|
||||
/* Check if the Path is symbolic link */
|
||||
fun Path.safeSymbolicRead(): Path = if (isSymbolicLink()) readSymbolicLink() else this
|
||||
|
@ -93,26 +93,16 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
* or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise.
|
||||
*/
|
||||
private fun setOrDecorateURLStreamHandlerFactory() {
|
||||
// Retrieve the `URL.factory` field
|
||||
val factoryField = URL::class.java.getDeclaredField("factory")
|
||||
// Make it accessible
|
||||
factoryField.isAccessible = true
|
||||
|
||||
// Check for preset factory, set directly if missing
|
||||
val existingFactory: URLStreamHandlerFactory? = factoryField.get(null) as URLStreamHandlerFactory?
|
||||
if (existingFactory == null) {
|
||||
try {
|
||||
URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory)
|
||||
}
|
||||
// Otherwise, decorate the existing and replace via reflection
|
||||
// as calling `URL.setURLStreamHandlerFactory` again will throw an error
|
||||
else {
|
||||
} catch (e: Error) {
|
||||
log.warn("The URLStreamHandlerFactory was already set in the JVM. Please be aware that this is not recommended.")
|
||||
val factoryField = URL::class.java.getDeclaredField("factory").apply { isAccessible = true }
|
||||
// Retrieve the field "streamHandlerLock" of the class URL that
|
||||
// is the lock used to synchronize access to the protocol handlers
|
||||
val lockField = URL::class.java.getDeclaredField("streamHandlerLock")
|
||||
val lockField = URL::class.java.getDeclaredField("streamHandlerLock").apply { isAccessible = true }
|
||||
// It is a private field so we need to make it accessible
|
||||
// Note: this will only work as-is in JDK8.
|
||||
lockField.isAccessible = true
|
||||
val existingFactory = factoryField.get(null) as URLStreamHandlerFactory?
|
||||
// Use the same lock to reset the factory
|
||||
synchronized(lockField.get(null)) {
|
||||
// Reset the value to prevent Error due to a factory already defined
|
||||
@ -121,7 +111,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
URL.setURLStreamHandlerFactory { protocol ->
|
||||
// route between our own and the pre-existing factory
|
||||
AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol)
|
||||
?: existingFactory.createURLStreamHandler(protocol)
|
||||
?: existingFactory?.createURLStreamHandler(protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.declaredField
|
||||
import org.assertj.core.api.Assertions.catchThrowable
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
@ -14,22 +13,17 @@ import kotlin.test.assertEquals
|
||||
class ByteArraysTest {
|
||||
@Test(timeout=300_000)
|
||||
fun `slice works`() {
|
||||
byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9).let {
|
||||
sliceWorksImpl(it, OpaqueBytesSubSequence(it, 2, 5))
|
||||
}
|
||||
byteArrayOf(0, 1, 2, 3, 4).let {
|
||||
sliceWorksImpl(it, OpaqueBytes(it))
|
||||
}
|
||||
sliceWorksImpl(OpaqueBytesSubSequence(byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9), 2, 5))
|
||||
sliceWorksImpl(OpaqueBytes(byteArrayOf(0, 1, 2, 3, 4)))
|
||||
}
|
||||
|
||||
private fun sliceWorksImpl(array: ByteArray, seq: ByteSequence) {
|
||||
private fun sliceWorksImpl(seq: ByteSequence) {
|
||||
// Python-style negative indices can be implemented later if needed:
|
||||
assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(-1) }.javaClass)
|
||||
assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(end = -1) }.javaClass)
|
||||
fun check(expected: ByteArray, actual: ByteBuffer) {
|
||||
assertEquals(ByteBuffer.wrap(expected), actual)
|
||||
assertSame(ReadOnlyBufferException::class.java, catchThrowable { actual.array() }.javaClass)
|
||||
assertSame(array, actual.declaredField<ByteArray>(ByteBuffer::class, "hb").value)
|
||||
}
|
||||
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice())
|
||||
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 5))
|
||||
@ -48,14 +42,14 @@ class ByteArraysTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test hex parsing strictly uppercase`() {
|
||||
val HEX_REGEX = "^[0-9A-F]+\$".toRegex()
|
||||
val hexRegex = "^[0-9A-F]+\$".toRegex()
|
||||
|
||||
val privacySalt = net.corda.core.contracts.PrivacySalt()
|
||||
val privacySaltAsHexString = privacySalt.bytes.toHexString()
|
||||
assertTrue(privacySaltAsHexString.matches(HEX_REGEX))
|
||||
assertTrue(privacySaltAsHexString.matches(hexRegex))
|
||||
|
||||
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
||||
val txhashAsHexString = stateRef.txhash.bytes.toHexString()
|
||||
assertTrue(txhashAsHexString.matches(HEX_REGEX))
|
||||
assertTrue(txhashAsHexString.matches(hexRegex))
|
||||
}
|
||||
}
|
||||
|
@ -35,17 +35,6 @@ shadowJar {
|
||||
version = null
|
||||
zip64 true
|
||||
exclude '**/Log4j2Plugins.dat'
|
||||
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver ' +
|
||||
'java.base/java.time java.base/java.io ' +
|
||||
'java.base/java.util java.base/java.net ' +
|
||||
'java.base/java.nio java.base/java.lang.invoke ' +
|
||||
'java.base/java.security.cert java.base/java.security ' +
|
||||
'java.base/javax.net.ssl java.base/java.util.concurrent ' +
|
||||
'java.sql/java.sql'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum ImageVariant {
|
||||
|
@ -64,9 +64,6 @@ task testJar(type: Jar) {
|
||||
task integrationTest(type: Test, dependsOn: []) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -1,5 +1,5 @@
|
||||
kotlin.incremental=true
|
||||
org.gradle.jvmargs=-Xmx6g -Dfile.encoding=UTF-8 --add-opens 'java.base/java.time=ALL-UNNAMED' --add-opens 'java.base/java.io=ALL-UNNAMED'
|
||||
org.gradle.jvmargs=-Xmx6g -Dfile.encoding=UTF-8'
|
||||
org.gradle.caching=false
|
||||
owasp.failOnError=false
|
||||
owasp.failBuildOnCVSS=11.0
|
||||
|
@ -104,10 +104,6 @@ artifacts {
|
||||
|
||||
jar {
|
||||
baseName 'corda-node-api'
|
||||
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.base/java.io java.base/java.time java.base/java.util java.base/java.lang.invoke java.base/java.security')
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
@ -17,9 +17,7 @@ import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
@ -28,6 +26,7 @@ import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.temporal.Temporal
|
||||
import java.time.temporal.TemporalAmount
|
||||
import java.util.Properties
|
||||
import java.util.UUID
|
||||
import javax.security.auth.x500.X500Principal
|
||||
@ -298,104 +297,45 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
|
||||
*/
|
||||
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
|
||||
|
||||
fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) {
|
||||
this
|
||||
} else if (this != null) {
|
||||
ConfigValueFactory.fromAnyRef(convertValue(this))
|
||||
} else {
|
||||
ConfigValueFactory.fromAnyRef(null)
|
||||
}
|
||||
fun Any?.toConfigValue(): ConfigValue = ConfigValueFactory.fromAnyRef(sanitiseForFromAnyRef(this))
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
|
||||
private fun Any.toConfigMap(): Map<String, Any> {
|
||||
val values = HashMap<String, Any>()
|
||||
private fun Any.toConfigMap(): Map<String, Any?> {
|
||||
val values = LinkedHashMap<String, Any?>()
|
||||
for (field in javaClass.declaredFields) {
|
||||
if (field.isStatic || field.isSynthetic) continue
|
||||
field.isAccessible = true
|
||||
val value = field.get(this) ?: continue
|
||||
val configValue = if (value is String || value is Boolean || value is Number) {
|
||||
// These types are supported by Config as use as is
|
||||
value
|
||||
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
|
||||
value is Path || value is URL || value is UUID || value is X500Principal) {
|
||||
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
|
||||
value.toString()
|
||||
} else if (value is Enum<*>) {
|
||||
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
|
||||
value.name
|
||||
} else if (value is Properties) {
|
||||
// For Properties we treat keys with . as nested configs
|
||||
ConfigFactory.parseMap(uncheckedCast(value)).root()
|
||||
} else if (value is Iterable<*>) {
|
||||
value.toConfigIterable(field)
|
||||
} else {
|
||||
// Else this is a custom object recursed over
|
||||
value.toConfigMap()
|
||||
}
|
||||
values[field.name] = configValue
|
||||
values[field.name] = sanitiseForFromAnyRef(value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
private fun convertValue(value: Any): Any {
|
||||
|
||||
return if (value is String || value is Boolean || value is Number) {
|
||||
/**
|
||||
* @see ConfigValueFactory.fromAnyRef
|
||||
*/
|
||||
private fun sanitiseForFromAnyRef(value: Any?): Any? {
|
||||
return when (value) {
|
||||
// These types are supported by Config as use as is
|
||||
value
|
||||
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
|
||||
value is Path || value is URL || value is UUID || value is X500Principal) {
|
||||
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
|
||||
value.toString()
|
||||
} else if (value is Enum<*>) {
|
||||
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
|
||||
value.name
|
||||
} else if (value is Properties) {
|
||||
is String, is Boolean, is Number, is ConfigValue, is Duration, null -> value
|
||||
is Enum<*> -> value.name
|
||||
// These types make sense to be represented as Strings
|
||||
is Temporal, is TemporalAmount, is NetworkHostAndPort, is CordaX500Name, is Path, is URL, is UUID, is X500Principal -> value.toString()
|
||||
// For Properties we treat keys with . as nested configs
|
||||
ConfigFactory.parseMap(uncheckedCast(value)).root()
|
||||
} else if (value is Iterable<*>) {
|
||||
value.toConfigIterable()
|
||||
} else {
|
||||
is Properties -> ConfigFactory.parseMap(uncheckedCast(value)).root()
|
||||
is Map<*, *> -> ConfigFactory.parseMap(value.map { it.key.toString() to sanitiseForFromAnyRef(it.value) }.toMap()).root()
|
||||
is Iterable<*> -> value.map(::sanitiseForFromAnyRef)
|
||||
// Else this is a custom object recursed over
|
||||
value.toConfigMap()
|
||||
else -> value.toConfigMap()
|
||||
}
|
||||
}
|
||||
|
||||
// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
|
||||
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
|
||||
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
|
||||
return when (elementType) {
|
||||
// For the types already supported by Config we can use the Iterable as is
|
||||
String::class.java -> this
|
||||
Integer::class.java -> this
|
||||
java.lang.Long::class.java -> this
|
||||
java.lang.Double::class.java -> this
|
||||
java.lang.Boolean::class.java -> this
|
||||
LocalDate::class.java -> map(Any?::toString)
|
||||
Instant::class.java -> map(Any?::toString)
|
||||
NetworkHostAndPort::class.java -> map(Any?::toString)
|
||||
Path::class.java -> map(Any?::toString)
|
||||
URL::class.java -> map(Any?::toString)
|
||||
X500Principal::class.java -> map(Any?::toString)
|
||||
UUID::class.java -> map(Any?::toString)
|
||||
CordaX500Name::class.java -> map(Any?::toString)
|
||||
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
|
||||
else -> if (elementType.isEnum) {
|
||||
map { (it as Enum<*>).name }
|
||||
} else {
|
||||
map { it?.toConfigMap() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
|
||||
|
||||
// The typesafe .getBoolean function is case sensitive, this is a case insensitive version
|
||||
fun Config.getBooleanCaseInsensitive(path: String): Boolean {
|
||||
try {
|
||||
return getBoolean(path)
|
||||
} catch (e: Exception) {
|
||||
val stringVal = getString(path).toLowerCase()
|
||||
val stringVal = getString(path).lowercase()
|
||||
if (stringVal == "true" || stringVal == "false") {
|
||||
return stringVal.toBoolean()
|
||||
}
|
||||
|
@ -6,6 +6,4 @@ data class User(
|
||||
val password: String,
|
||||
val permissions: Set<String>) {
|
||||
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
|
||||
@Deprecated("Use toConfig().root().unwrapped() instead", ReplaceWith("toConfig().root().unwrapped()"))
|
||||
fun toMap(): Map<String, Any> = toConfig().root().unwrapped()
|
||||
}
|
||||
|
@ -9,25 +9,18 @@ import javax.net.ssl.ManagerFactoryParameters
|
||||
import javax.net.ssl.X509ExtendedKeyManager
|
||||
import javax.net.ssl.X509KeyManager
|
||||
|
||||
class CertHoldingKeyManagerFactorySpiWrapper(private val factorySpi: KeyManagerFactorySpi, private val amqpConfig: AMQPConfiguration) : KeyManagerFactorySpi() {
|
||||
private class CertHoldingKeyManagerFactorySpiWrapper(private val keyManagerFactory: KeyManagerFactory,
|
||||
private val amqpConfig: AMQPConfiguration) : KeyManagerFactorySpi() {
|
||||
override fun engineInit(keyStore: KeyStore?, password: CharArray?) {
|
||||
val engineInitMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineInit", KeyStore::class.java, CharArray::class.java)
|
||||
engineInitMethod.isAccessible = true
|
||||
engineInitMethod.invoke(factorySpi, keyStore, password)
|
||||
keyManagerFactory.init(keyStore, password)
|
||||
}
|
||||
|
||||
override fun engineInit(spec: ManagerFactoryParameters?) {
|
||||
val engineInitMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineInit", ManagerFactoryParameters::class.java)
|
||||
engineInitMethod.isAccessible = true
|
||||
engineInitMethod.invoke(factorySpi, spec)
|
||||
keyManagerFactory.init(spec)
|
||||
}
|
||||
|
||||
private fun getKeyManagersImpl(): Array<KeyManager> {
|
||||
val engineGetKeyManagersMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineGetKeyManagers")
|
||||
engineGetKeyManagersMethod.isAccessible = true
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val keyManagers = engineGetKeyManagersMethod.invoke(factorySpi) as Array<KeyManager>
|
||||
return if (factorySpi is CertHoldingKeyManagerFactorySpiWrapper) keyManagers else keyManagers.map {
|
||||
return keyManagerFactory.keyManagers.map {
|
||||
val aliasProvidingKeyManager = getDefaultKeyManager(it)
|
||||
// Use the SNIKeyManager if keystore has several entries and only for clients and non-openSSL servers.
|
||||
// Condition of using SNIKeyManager: if its client, or JDKSsl server.
|
||||
@ -62,15 +55,11 @@ class CertHoldingKeyManagerFactorySpiWrapper(private val factorySpi: KeyManagerF
|
||||
* the wrapper is not thread safe as in it will return the last used alias/cert chain and has itself no notion
|
||||
* of belonging to a certain channel.
|
||||
*/
|
||||
class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration) : KeyManagerFactory(getFactorySpi(factory, amqpConfig), factory.provider, factory.algorithm) {
|
||||
companion object {
|
||||
private fun getFactorySpi(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration): KeyManagerFactorySpi {
|
||||
val spiField = KeyManagerFactory::class.java.getDeclaredField("factorySpi")
|
||||
spiField.isAccessible = true
|
||||
return CertHoldingKeyManagerFactorySpiWrapper(spiField.get(factory) as KeyManagerFactorySpi, amqpConfig)
|
||||
}
|
||||
}
|
||||
|
||||
class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration) : KeyManagerFactory(
|
||||
CertHoldingKeyManagerFactorySpiWrapper(factory, amqpConfig),
|
||||
factory.provider,
|
||||
factory.algorithm
|
||||
) {
|
||||
fun getCurrentCertChain(): Array<out X509Certificate>? {
|
||||
val keyManager = keyManagers.firstOrNull()
|
||||
val alias = if (keyManager is AliasProvidingKeyMangerWrapper) keyManager.lastAlias else null
|
||||
|
@ -7,34 +7,24 @@ import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.TrustManagerFactorySpi
|
||||
import javax.net.ssl.X509ExtendedTrustManager
|
||||
|
||||
class LoggingTrustManagerFactorySpiWrapper(private val factorySpi: TrustManagerFactorySpi) : TrustManagerFactorySpi() {
|
||||
class LoggingTrustManagerFactorySpiWrapper(private val trustManagerFactory: TrustManagerFactory) : TrustManagerFactorySpi() {
|
||||
override fun engineGetTrustManagers(): Array<TrustManager> {
|
||||
val engineGetTrustManagersMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineGetTrustManagers")
|
||||
engineGetTrustManagersMethod.isAccessible = true
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val trustManagers = engineGetTrustManagersMethod.invoke(factorySpi) as Array<TrustManager>
|
||||
return if (factorySpi is LoggingTrustManagerFactorySpiWrapper) trustManagers else trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray()
|
||||
return trustManagerFactory.trustManagers
|
||||
.mapNotNull { (it as? X509ExtendedTrustManager)?.let(::LoggingTrustManagerWrapper) }
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
override fun engineInit(ks: KeyStore?) {
|
||||
val engineInitMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineInit", KeyStore::class.java)
|
||||
engineInitMethod.isAccessible = true
|
||||
engineInitMethod.invoke(factorySpi, ks)
|
||||
trustManagerFactory.init(ks)
|
||||
}
|
||||
|
||||
override fun engineInit(spec: ManagerFactoryParameters?) {
|
||||
val engineInitMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineInit", ManagerFactoryParameters::class.java)
|
||||
engineInitMethod.isAccessible = true
|
||||
engineInitMethod.invoke(factorySpi, spec)
|
||||
trustManagerFactory.init(spec)
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory(getFactorySpi(factory), factory.provider, factory.algorithm) {
|
||||
companion object {
|
||||
private fun getFactorySpi(factory: TrustManagerFactory): TrustManagerFactorySpi {
|
||||
val spiField = TrustManagerFactory::class.java.getDeclaredField("factorySpi")
|
||||
spiField.isAccessible = true
|
||||
return LoggingTrustManagerFactorySpiWrapper(spiField.get(factory) as TrustManagerFactorySpi)
|
||||
}
|
||||
}
|
||||
}
|
||||
class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory(
|
||||
LoggingTrustManagerFactorySpiWrapper(factory),
|
||||
factory.provider,
|
||||
factory.algorithm
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.rpc.client
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.serialization.internal.NotSerializableException
|
||||
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import rx.Observable
|
||||
@ -20,9 +21,7 @@ class RpcClientCordaFutureSerializer (factory: SerializerFactory)
|
||||
try {
|
||||
return proxy.observable.toFuture()
|
||||
} catch (e: NotSerializableException) {
|
||||
throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n").apply {
|
||||
initCause(e.cause)
|
||||
}
|
||||
throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n", e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
package net.corda.nodeapi.internal.serialization.kryo
|
||||
|
||||
import net.corda.core.internal.declaredField
|
||||
import net.corda.serialization.internal.ByteBufferOutputStream
|
||||
import org.assertj.core.api.Assertions.catchThrowable
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Test
|
||||
import java.io.*
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.BufferOverflowException
|
||||
import java.util.*
|
||||
import java.util.Random
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
import java.util.zip.InflaterInputStream
|
||||
import kotlin.test.assertEquals
|
||||
@ -67,15 +67,12 @@ class KryoStreamsTest {
|
||||
fun `ByteBufferOutputStream works`() {
|
||||
val stream = ByteBufferOutputStream(3)
|
||||
stream.write("abc".toByteArray())
|
||||
val getBuf = stream.declaredField<ByteArray>(ByteArrayOutputStream::class, "buf")::value
|
||||
assertEquals(3, getBuf().size)
|
||||
repeat(2) {
|
||||
assertSame<Any>(BufferOverflowException::class.java, catchThrowable {
|
||||
stream.alsoAsByteBuffer(9) {
|
||||
it.put("0123456789".toByteArray())
|
||||
}
|
||||
}.javaClass)
|
||||
assertEquals(3 + 9, getBuf().size)
|
||||
}
|
||||
// This time make too much space:
|
||||
stream.alsoAsByteBuffer(11) {
|
||||
|
@ -280,10 +280,6 @@ tasks.register('integrationTest', Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
maxParallelForks = (System.env.CORDA_NODE_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_NODE_INT_TESTING_FORKS".toInteger()
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
|
||||
// CertificateRevocationListNodeTests
|
||||
systemProperty 'net.corda.dpcrl.connect.timeout', '4000'
|
||||
}
|
||||
@ -292,9 +288,6 @@ tasks.register('slowIntegrationTest', Test) {
|
||||
testClassesDirs = sourceSets.slowIntegrationTest.output.classesDirs
|
||||
classpath = sourceSets.slowIntegrationTest.runtimeClasspath
|
||||
maxParallelForks = 1
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
// quasar exclusions upon agent code instrumentation at run-time
|
||||
@ -332,9 +325,6 @@ quasar {
|
||||
|
||||
jar {
|
||||
baseName 'corda-node'
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.base/java.time java.base/java.io java.base/java.util java.base/java.net')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('test', Test) {
|
||||
|
@ -57,15 +57,15 @@ tasks.register('buildCordaJAR', FatCapsule) {
|
||||
with jar
|
||||
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver java.base/java.net java.base/java.lang java.base/java.time')
|
||||
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver')
|
||||
}
|
||||
|
||||
capsuleManifest {
|
||||
applicationVersion = corda_release_version
|
||||
applicationId = "net.corda.node.Corda"
|
||||
// See experimental/quasar-hook/README.md for how to generate.
|
||||
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;org.mockito**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;io.opentelemetry**)"
|
||||
def quasarClassLoaderExclusion = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)"
|
||||
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;org.mockito**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;io.opentelemetry**)"
|
||||
def quasarClassLoaderExclusion = "l(net.corda.core.serialization.internal.**)"
|
||||
def quasarOptions = "m"
|
||||
javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarOptions}${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"]
|
||||
systemProperties['visualvm.display.name'] = 'Corda'
|
||||
@ -73,7 +73,6 @@ tasks.register('buildCordaJAR', FatCapsule) {
|
||||
|
||||
// JVM configuration:
|
||||
// - Constrain to small heap sizes to ease development on low end devices.
|
||||
// - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup.
|
||||
// NOTE: these can be overridden in node.conf.
|
||||
//
|
||||
// If you change these flags, please also update Driver.kt
|
||||
|
@ -2,23 +2,32 @@
|
||||
// must also be in the default package. When using Kotlin there are a whole host of exceptions
|
||||
// trying to construct this from Capsule, so it is written in Java.
|
||||
|
||||
import com.typesafe.config.*;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import com.typesafe.config.ConfigParseOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import sun.misc.Signal;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.typesafe.config.ConfigUtil.splitPath;
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
public class CordaCaplet extends Capsule {
|
||||
@ -47,7 +56,7 @@ public class CordaCaplet extends Capsule {
|
||||
|
||||
File getConfigFile(List<String> args, String baseDir) {
|
||||
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
|
||||
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
|
||||
return (config == null || config.isEmpty()) ? new File(baseDir, "node.conf") : new File(config);
|
||||
}
|
||||
|
||||
String getBaseDirectory(List<String> args) {
|
||||
@ -77,7 +86,7 @@ public class CordaCaplet extends Capsule {
|
||||
}
|
||||
|
||||
if (arg.toLowerCase().startsWith(lowerCaseOption)) {
|
||||
if (arg.length() > option.length() && arg.substring(option.length(), option.length() + 1).equals("=")) {
|
||||
if (arg.length() > option.length() && arg.charAt(option.length()) == '=') {
|
||||
return arg.substring(option.length() + 1);
|
||||
} else {
|
||||
return null;
|
||||
@ -109,26 +118,18 @@ public class CordaCaplet extends Capsule {
|
||||
// For multiple instances Capsule jvm args handling works on basis that one overrides the other.
|
||||
@Override
|
||||
protected int launch(ProcessBuilder pb) throws IOException, InterruptedException {
|
||||
if (isAtLeastJavaVersion11()) {
|
||||
List<String> args = pb.command();
|
||||
List<String> myArgs = Arrays.asList(
|
||||
"--add-opens=java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang.invoke=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.time=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens=java.base/javax.net.ssl=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.security.cert=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio=ALL-UNNAMED");
|
||||
args.addAll(1, myArgs);
|
||||
args.addAll(1, getNodeJvmArgs());
|
||||
pb.command(args);
|
||||
}
|
||||
return super.launch(pb);
|
||||
}
|
||||
|
||||
private List<String> getNodeJvmArgs() throws IOException {
|
||||
try (InputStream resource = requireNonNull(getClass().getResourceAsStream("/node-jvm-args.txt"))) {
|
||||
return new BufferedReader(new InputStreamReader(resource)).lines().collect(toList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overriding the Caplet classpath generation via the intended interface in Capsule.
|
||||
*/
|
||||
@ -148,12 +149,12 @@ public class CordaCaplet extends Capsule {
|
||||
}
|
||||
|
||||
// Add additional directories of JARs to the classpath (at the end), e.g., for JDBC drivers.
|
||||
augmentClasspath((List<Path>) cp, new File(baseDir, "drivers"));
|
||||
augmentClasspath((List<Path>) cp, Path.of(baseDir, "drivers"));
|
||||
try {
|
||||
List<String> jarDirs = nodeConfig.getStringList("jarDirs");
|
||||
log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs);
|
||||
for (String jarDir : jarDirs) {
|
||||
augmentClasspath((List<Path>) cp, new File(jarDir));
|
||||
augmentClasspath((List<Path>) cp, Path.of(jarDir));
|
||||
}
|
||||
} catch (ConfigException.Missing e) {
|
||||
// Ignore since it's ok to be Missing. Other errors would be unexpected.
|
||||
@ -183,9 +184,6 @@ public class CordaCaplet extends Capsule {
|
||||
jvmArgs.add("-XX:+HeapDumpOnOutOfMemoryError");
|
||||
jvmArgs.add("-XX:+CrashOnOutOfMemoryError");
|
||||
}
|
||||
if (isAtLeastJavaVersion11()) {
|
||||
jvmArgs.add("-Dnashorn.args=--no-deprecation-warning");
|
||||
}
|
||||
return (T) jvmArgs;
|
||||
} else if (ATTR_SYSTEM_PROPERTIES == attr) {
|
||||
// Add system properties, if specified, from the config.
|
||||
@ -193,7 +191,7 @@ public class CordaCaplet extends Capsule {
|
||||
try {
|
||||
Map<String, ?> overrideSystemProps = nodeConfig.getConfig("systemProperties").entrySet().stream()
|
||||
.map(Property::create)
|
||||
.collect(toMap(Property::getKey, Property::getValue));
|
||||
.collect(toMap(Property::key, Property::value));
|
||||
log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps);
|
||||
for (Map.Entry<String, ?> entry : overrideSystemProps.entrySet()) {
|
||||
systemProps.put(entry.getKey(), entry.getValue().toString());
|
||||
@ -207,18 +205,15 @@ public class CordaCaplet extends Capsule {
|
||||
} else return super.attribute(attr);
|
||||
}
|
||||
|
||||
private void augmentClasspath(List<Path> classpath, File dir) {
|
||||
try {
|
||||
if (dir.exists()) {
|
||||
// The following might return null if the directory is not there (we check this already) or if an I/O error occurs.
|
||||
for (File file : dir.listFiles()) {
|
||||
addToClasspath(classpath, file);
|
||||
private void augmentClasspath(List<Path> classpath, Path dir) {
|
||||
if (Files.exists(dir)) {
|
||||
try (var files = Files.list(dir)) {
|
||||
files.forEach((file) -> addToClasspath(classpath, file));
|
||||
} catch (IOException e) {
|
||||
log(LOG_QUIET, e);
|
||||
}
|
||||
} else {
|
||||
log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.getAbsolutePath());
|
||||
}
|
||||
} catch (SecurityException | NullPointerException e) {
|
||||
log(LOG_QUIET, e);
|
||||
log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.toAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,14 +225,6 @@ public class CordaCaplet extends Capsule {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAtLeastJavaVersion11() {
|
||||
String version = System.getProperty("java.specification.version");
|
||||
if (version != null) {
|
||||
return Float.parseFloat(version) >= 11f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean checkIfCordappDirExists(File dir) {
|
||||
try {
|
||||
if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case.
|
||||
@ -256,18 +243,18 @@ public class CordaCaplet extends Capsule {
|
||||
log(LOG_VERBOSE, "Cordapps dir could not be created");
|
||||
}
|
||||
|
||||
private void addToClasspath(List<Path> classpath, File file) {
|
||||
private void addToClasspath(List<Path> classpath, Path file) {
|
||||
try {
|
||||
if (file.canRead()) {
|
||||
if (file.isFile() && isJAR(file)) {
|
||||
classpath.add(file.toPath().toAbsolutePath());
|
||||
} else if (file.isDirectory()) { // Search in nested folders as well. TODO: check for circular symlinks.
|
||||
if (Files.isReadable(file)) {
|
||||
if (Files.isRegularFile(file) && isJAR(file)) {
|
||||
classpath.add(file.toAbsolutePath());
|
||||
} else if (Files.isDirectory(file)) { // Search in nested folders as well. TODO: check for circular symlinks.
|
||||
augmentClasspath(classpath, file);
|
||||
}
|
||||
} else {
|
||||
log(LOG_VERBOSE, "File or directory to add in Classpath could not be read " + file.getAbsolutePath());
|
||||
log(LOG_VERBOSE, "File or directory to add in Classpath could not be read " + file.toAbsolutePath());
|
||||
}
|
||||
} catch (SecurityException | NullPointerException e) {
|
||||
} catch (SecurityException e) {
|
||||
log(LOG_QUIET, e);
|
||||
}
|
||||
}
|
||||
@ -280,30 +267,14 @@ public class CordaCaplet extends Capsule {
|
||||
});
|
||||
}
|
||||
|
||||
private Boolean isJAR(File file) {
|
||||
return file.getName().toLowerCase().endsWith(".jar");
|
||||
private Boolean isJAR(Path file) {
|
||||
return file.toString().toLowerCase().endsWith(".jar");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class so that we can parse the "systemProperties" element of node.conf.
|
||||
*/
|
||||
private static class Property {
|
||||
private final String key;
|
||||
private final Object value;
|
||||
|
||||
Property(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
private record Property(String key, Object value) {
|
||||
static Property create(Map.Entry<String, ConfigValue> entry) {
|
||||
// String.join is preferred here over Typesafe's joinPath method, as the joinPath method would put quotes around the system
|
||||
// property key which is undesirable here.
|
||||
|
9
node/capsule/src/main/resources/node-jvm-args.txt
Normal file
9
node/capsule/src/main/resources/node-jvm-args.txt
Normal file
@ -0,0 +1,9 @@
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED
|
||||
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
|
||||
--add-opens=java.base/java.nio=ALL-UNNAMED
|
||||
--add-opens=java.base/java.security=ALL-UNNAMED
|
||||
--add-opens=java.base/java.security.cert=ALL-UNNAMED
|
||||
--add-opens=java.base/java.time=ALL-UNNAMED
|
||||
--add-opens=java.base/java.util=ALL-UNNAMED
|
||||
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED
|
||||
--add-opens=java.sql/java.sql=ALL-UNNAMED
|
@ -1,53 +1,12 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.core.internal.DeclaredField
|
||||
import net.corda.core.internal.staticField
|
||||
import net.corda.node.internal.Node
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy
|
||||
import java.io.ObjectInputFilter
|
||||
import java.io.ObjectInputFilter.Status
|
||||
|
||||
internal object SerialFilter {
|
||||
private val filterInterface: Class<*>
|
||||
private val serialClassGetter: Method
|
||||
private val undecided: Any
|
||||
private val rejected: Any
|
||||
private val serialFilterLock: Any
|
||||
private val serialFilterField: DeclaredField<Any>
|
||||
|
||||
init {
|
||||
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports:
|
||||
fun getFilterInterface(packageName: String): Class<*>? {
|
||||
return try {
|
||||
Class.forName("$packageName.ObjectInputFilter")
|
||||
} catch (e: ClassNotFoundException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
|
||||
filterInterface = getFilterInterface("java.io")
|
||||
?: getFilterInterface("sun.misc")
|
||||
?: Node.failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121.")
|
||||
serialClassGetter = Class.forName("${filterInterface.name}\$FilterInfo").getMethod("serialClass")
|
||||
val statusEnum = Class.forName("${filterInterface.name}\$Status")
|
||||
undecided = statusEnum.getField("UNDECIDED").get(null)
|
||||
rejected = statusEnum.getField("REJECTED").get(null)
|
||||
val configClass = Class.forName("${filterInterface.name}\$Config")
|
||||
serialFilterLock = configClass.staticField<Any>("serialFilterLock").value
|
||||
serialFilterField = configClass.staticField("serialFilter")
|
||||
}
|
||||
|
||||
internal fun install(acceptClass: (Class<*>) -> Boolean) {
|
||||
val filter = Proxy.newProxyInstance(javaClass.classLoader, arrayOf(filterInterface)) { _, _, args ->
|
||||
val serialClass = serialClassGetter.invoke(args[0]) as Class<*>?
|
||||
if (applyPredicate(acceptClass, serialClass)) {
|
||||
undecided
|
||||
} else {
|
||||
rejected
|
||||
}
|
||||
}
|
||||
// Can't simply use the setter as in non-trampoline mode Capsule has inited the filter in premain:
|
||||
synchronized(serialFilterLock) {
|
||||
serialFilterField.value = filter
|
||||
ObjectInputFilter.Config.setSerialFilter { filterInfo ->
|
||||
if (applyPredicate(acceptClass, filterInfo.serialClass())) Status.UNDECIDED else Status.REJECTED
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
import java.lang.ProcessBuilder.Redirect
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.nio.file.Files
|
||||
@ -179,15 +180,18 @@ class ExternalVerifierHandle(private val serviceHub: ServiceHubInternal) : AutoC
|
||||
val fromVerifier: DataInputStream
|
||||
|
||||
init {
|
||||
val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories()
|
||||
val command = listOf(
|
||||
"${Path(System.getProperty("java.home"), "bin", "java")}",
|
||||
val inheritedJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments.filter { "--add-opens" in it }
|
||||
val command = ArrayList<String>()
|
||||
command += "${Path(System.getProperty("java.home"), "bin", "java")}"
|
||||
command += inheritedJvmArgs
|
||||
command += listOf(
|
||||
"-jar",
|
||||
"$verifierJar",
|
||||
"${server.localPort}",
|
||||
System.getProperty("log4j2.level")?.lowercase() ?: "info" // TODO
|
||||
System.getProperty("log4j2.level")?.lowercase() ?: "info"
|
||||
)
|
||||
log.debug { "Verifier command: $command" }
|
||||
val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories()
|
||||
verifierProcess = ProcessBuilder(command)
|
||||
.redirectOutput(Redirect.appendTo((logsDirectory / "verifier-stdout.log").toFile()))
|
||||
.redirectError(Redirect.appendTo((logsDirectory / "verifier-stderr.log").toFile()))
|
||||
|
@ -78,9 +78,6 @@ dependencies {
|
||||
task integrationTest(type: Test, dependsOn: []) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
def nodeTask = tasks.getByPath(':node:capsule:assemble')
|
||||
@ -147,9 +144,6 @@ task runSender(type: JavaExec, dependsOn: jar) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'net.corda.attachmentdemo.AttachmentDemoKt'
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
|
||||
args '--role'
|
||||
args 'SENDER'
|
||||
}
|
||||
@ -158,9 +152,6 @@ task runRecipient(type: JavaExec, dependsOn: jar) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'net.corda.attachmentdemo.AttachmentDemoKt'
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
|
||||
args '--role'
|
||||
args 'RECIPIENT'
|
||||
}
|
||||
|
@ -5,28 +5,23 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.Permissions.Companion.all
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.DummyClusterSpec
|
||||
import net.corda.testing.node.internal.findCordapp
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CompletableFuture.supplyAsync
|
||||
|
||||
class AttachmentDemoTest {
|
||||
// run with a 10,000,000 bytes in-memory zip file. In practice, a slightly bigger file will be used (~10,002,000 bytes).
|
||||
@Test(timeout=300_000)
|
||||
fun `attachment demo using a 10MB zip file`() {
|
||||
val numOfExpectedBytes = 10_000_000
|
||||
driver(DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = true,
|
||||
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")),
|
||||
notarySpecs = listOf(NotarySpec(name = DUMMY_NOTARY_NAME, cluster = DummyClusterSpec(clusterSize = 1))))
|
||||
) {
|
||||
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows"))
|
||||
)) {
|
||||
val demoUser = listOf(User("demo", "demo", setOf(all())))
|
||||
val (nodeA, nodeB) = listOf(
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
||||
|
@ -7,7 +7,7 @@ import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.internal.InputStreamAndHash
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startTrackedFlow
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -16,9 +16,14 @@ import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.jar.JarInputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import javax.servlet.http.HttpServletResponse.SC_OK
|
||||
import javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION
|
||||
import javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM
|
||||
import kotlin.io.path.createTempFile
|
||||
import kotlin.io.path.inputStream
|
||||
import kotlin.io.path.outputStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
internal enum class Role {
|
||||
@ -57,11 +62,16 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
/** An in memory test zip attachment of at least numOfClearBytes size, will be used. */
|
||||
/** A temp zip file attachment of at least numOfClearBytes size, will be used. */
|
||||
// DOCSTART 2
|
||||
fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
|
||||
val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0)
|
||||
sender(rpc, inputStream, hash)
|
||||
val attachmentFile = createTempFile("attachment-demo").apply { toFile().deleteOnExit() }
|
||||
ZipOutputStream(attachmentFile.outputStream()).use { zip ->
|
||||
zip.putNextEntry(ZipEntry("test"))
|
||||
zip.write(ByteArray(numOfClearBytes))
|
||||
zip.closeEntry()
|
||||
}
|
||||
sender(rpc, attachmentFile.inputStream(), attachmentFile.hash)
|
||||
}
|
||||
|
||||
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256) {
|
||||
|
@ -117,36 +117,24 @@ tasks.register('runRPCCashIssue', JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass = 'net.corda.bank.IssueCash'
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
|
||||
args '--role'
|
||||
args 'ISSUE_CASH_RPC'
|
||||
args '--quantity'
|
||||
args 20000
|
||||
args '--currency'
|
||||
args 'USD'
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
tasks.register('runWebCashIssue', JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass = 'net.corda.bank.IssueCash'
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
|
||||
args '--role'
|
||||
args 'ISSUE_CASH_WEB'
|
||||
args '--quantity'
|
||||
args 30000
|
||||
args '--currency'
|
||||
args 'GBP'
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -170,9 +170,6 @@ task deployNodes(type: net.corda.plugins.Cordform) {
|
||||
task integrationTest(type: Test, dependsOn: []) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
cordapp {
|
||||
|
@ -73,9 +73,6 @@ dependencies {
|
||||
task integrationTest(type: Test, dependsOn: []) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
configurations.cordaCordapp.canBeResolved = true
|
||||
@ -149,16 +146,6 @@ task deployNodes(type: net.corda.plugins.Cordform) {
|
||||
|
||||
jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver ' +
|
||||
'java.base/java.time java.base/java.io ' +
|
||||
'java.base/java.util java.base/java.net ' +
|
||||
'java.base/java.nio java.base/java.lang.invoke ' +
|
||||
'java.base/java.security.cert java.base/java.security ' +
|
||||
'java.base/javax.net.ssl java.base/java.util.concurrent ' +
|
||||
'java.sql/java.sql'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
|
@ -54,7 +54,6 @@ 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.assertThatIllegalArgumentException
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.assertj.core.api.Assertions.catchThrowable
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
@ -73,6 +72,7 @@ import org.junit.runners.Parameterized.Parameters
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.NotSerializableException
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
@ -148,13 +148,13 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
|
||||
data class Foo(val bar: String, val pub: Int)
|
||||
|
||||
data class testFloat(val f: Float)
|
||||
data class TestFloat(val f: Float)
|
||||
|
||||
data class testDouble(val d: Double)
|
||||
data class TestDouble(val d: Double)
|
||||
|
||||
data class testShort(val s: Short)
|
||||
data class TestShort(val s: Short)
|
||||
|
||||
data class testBoolean(val b: Boolean)
|
||||
data class TestBoolean(val b: Boolean)
|
||||
|
||||
interface FooInterface {
|
||||
val pub: Int
|
||||
@ -340,25 +340,25 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test float`() {
|
||||
val obj = testFloat(10.0F)
|
||||
val obj = TestFloat(10.0F)
|
||||
serdes(obj)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test double`() {
|
||||
val obj = testDouble(10.0)
|
||||
val obj = TestDouble(10.0)
|
||||
serdes(obj)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test short`() {
|
||||
val obj = testShort(1)
|
||||
val obj = TestShort(1)
|
||||
serdes(obj)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test bool`() {
|
||||
val obj = testBoolean(true)
|
||||
val obj = TestBoolean(true)
|
||||
serdes(obj)
|
||||
}
|
||||
|
||||
@ -377,7 +377,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
@Test(timeout=300_000)
|
||||
fun `test dislike of HashMap`() {
|
||||
val obj = WrapHashMap(HashMap())
|
||||
assertThatIllegalArgumentException().isThrownBy {
|
||||
assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy {
|
||||
serdes(obj)
|
||||
}
|
||||
}
|
||||
@ -1303,7 +1303,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
)
|
||||
factory2.register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer)
|
||||
val bytes = ByteArray(10) { it.toByte() }
|
||||
val obj = bytes.inputStream()
|
||||
val obj: InputStream = bytes.inputStream()
|
||||
val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false)
|
||||
val obj3 = bytes.inputStream() // Can't use original since the stream pointer has moved.
|
||||
assertEquals(obj3.available(), obj2.available())
|
||||
|
@ -57,10 +57,6 @@ artifacts {
|
||||
jar {
|
||||
archiveBaseName = 'corda-serialization'
|
||||
archiveClassifier = ''
|
||||
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.base/java.time java.base/java.io')
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
@ -43,14 +43,8 @@ class ByteBufferInputStream(val byteBuffer: ByteBuffer) : InputStream() {
|
||||
}
|
||||
|
||||
class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) {
|
||||
companion object {
|
||||
private val ensureCapacity = ByteArrayOutputStream::class.java.getDeclaredMethod("ensureCapacity", Int::class.java).apply {
|
||||
isAccessible = true
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T {
|
||||
ensureCapacity.invoke(this, count + remaining)
|
||||
ensureCapacity(count + remaining)
|
||||
val buffer = ByteBuffer.wrap(buf, count, remaining)
|
||||
val result = task(buffer)
|
||||
count = buffer.position()
|
||||
@ -60,4 +54,10 @@ class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) {
|
||||
fun copyTo(stream: OutputStream) {
|
||||
stream.write(buf, 0, count)
|
||||
}
|
||||
|
||||
private fun ensureCapacity(minCapacity: Int) {
|
||||
if (minCapacity > buf.size) {
|
||||
buf = buf.copyOf(minCapacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import java.io.NotSerializableException
|
||||
|
||||
@Suppress("FunctionNaming")
|
||||
fun NotSerializableException(message: String?, cause: Throwable?): NotSerializableException {
|
||||
return NotSerializableException(message).apply { initCause(cause) }
|
||||
}
|
@ -1,19 +1,11 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.serialization.internal.NotSerializableException
|
||||
import org.slf4j.Logger
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Not a public property so will have to use reflection
|
||||
*/
|
||||
private fun Throwable.setMessage(newMsg: String) {
|
||||
val detailMessageField = Throwable::class.java.getDeclaredField("detailMessage")
|
||||
detailMessageField.isAccessible = true
|
||||
detailMessageField.set(this, newMsg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function which helps tracking the path in the object graph when exceptions are thrown.
|
||||
* Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue.
|
||||
@ -22,15 +14,13 @@ private fun Throwable.setMessage(newMsg: String) {
|
||||
internal inline fun <T> ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T {
|
||||
try {
|
||||
return block()
|
||||
} catch (th: Throwable) {
|
||||
when (th) {
|
||||
is AMQPNotSerializableException -> th.classHierarchy.add(strToAppendFn())
|
||||
// Do not overwrite the message of these exceptions as it may be used.
|
||||
is ClassNotFoundException -> {}
|
||||
is NoClassDefFoundError -> {}
|
||||
else -> th.setMessage("${strToAppendFn()} -> ${th.message}")
|
||||
}
|
||||
throw th
|
||||
} catch (e: AMQPNotSerializableException) {
|
||||
e.classHierarchy += strToAppendFn()
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
// Avoid creating heavily nested NotSerializableExceptions
|
||||
val cause = if (e.message?.contains(" -> ") == true) { e.cause ?: e } else { e }
|
||||
throw NotSerializableException("${strToAppendFn()} -> ${e.message}", cause)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,8 +67,3 @@ open class AMQPNotSerializableException(
|
||||
logger.debug("", cause)
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticParameterException(type: Type) : AMQPNotSerializableException(
|
||||
type,
|
||||
"Type '${type.typeName} has synthetic "
|
||||
+ "fields and is likely a nested inner class. This is not support by the Corda AMQP serialization framework")
|
@ -11,6 +11,7 @@ import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.serialization.internal.ByteBufferInputStream
|
||||
import net.corda.serialization.internal.CordaSerializationEncoding
|
||||
import net.corda.serialization.internal.NotSerializableException
|
||||
import net.corda.serialization.internal.NullEncodingWhitelist
|
||||
import net.corda.serialization.internal.SectionId
|
||||
import net.corda.serialization.internal.encodingNotPermittedFormat
|
||||
@ -120,11 +121,11 @@ class DeserializationInput constructor(
|
||||
return generator()
|
||||
} catch (amqp : AMQPNotSerializableException) {
|
||||
amqp.log("Deserialize", logger)
|
||||
throw NotSerializableException(amqp.mitigation)
|
||||
throw NotSerializableException(amqp.mitigation, amqp)
|
||||
} catch (nse: NotSerializableException) {
|
||||
throw nse
|
||||
} catch (e: Exception) {
|
||||
throw NotSerializableException("Internal deserialization failure: ${e.javaClass.name}: ${e.message}").apply { initCause(e) }
|
||||
throw NotSerializableException("Internal deserialization failure: ${e.javaClass.name}: ${e.message}", e)
|
||||
} finally {
|
||||
objectHistory.clear()
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.serialization.internal.NotSerializableException
|
||||
import net.corda.serialization.internal.model.LocalConstructorInformation
|
||||
import net.corda.serialization.internal.model.LocalPropertyInformation
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation
|
||||
@ -32,17 +33,12 @@ private class ConstructorCaller(private val javaConstructor: Constructor<Any>) :
|
||||
try {
|
||||
javaConstructor.newInstance(*parameters)
|
||||
} catch (e: InvocationTargetException) {
|
||||
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
|
||||
throw NotSerializableException(
|
||||
"Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " +
|
||||
"failed when called with parameters ${parameters.toList()}: ${e.cause!!.message}"
|
||||
"Constructor for ${javaConstructor.declaringClass.name} failed when called with parameters ${parameters.asList()}: ${e.cause?.message}",
|
||||
e.cause
|
||||
)
|
||||
} catch (e: IllegalAccessException) {
|
||||
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
|
||||
throw NotSerializableException(
|
||||
"Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " +
|
||||
"not accessible: ${e.message}"
|
||||
)
|
||||
throw NotSerializableException("Constructor for ${javaConstructor.declaringClass.name} not accessible: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ package net.corda.serialization.internal.amqp.custom
|
||||
|
||||
import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.serialization.internal.NotSerializableException
|
||||
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import java.io.NotSerializableException
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
@ -23,9 +23,7 @@ class CertPathSerializer(
|
||||
val cf = CertificateFactory.getInstance(proxy.type)
|
||||
return cf.generateCertPath(proxy.encoded.inputStream())
|
||||
} catch (ce: CertificateException) {
|
||||
val nse = NotSerializableException("java.security.cert.CertPath: $type")
|
||||
nse.initCause(ce)
|
||||
throw nse
|
||||
throw NotSerializableException("java.security.cert.CertPath: $type", ce)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,7 @@ import java.lang.reflect.Type
|
||||
/**
|
||||
* A serializer that writes out the content of an input stream as bytes and deserializes into a [ByteArrayInputStream].
|
||||
*/
|
||||
object InputStreamSerializer
|
||||
: CustomSerializer.Implements<InputStream>(
|
||||
InputStream::class.java
|
||||
) {
|
||||
override val revealSubclassesInSchema: Boolean = true
|
||||
|
||||
object InputStreamSerializer : CustomSerializer.Implements<InputStream>(InputStream::class.java) {
|
||||
override val schemaForDocumentation = Schema(
|
||||
listOf(
|
||||
RestrictedType(
|
||||
|
@ -2,7 +2,6 @@ package net.corda.serialization.internal.amqp.custom
|
||||
|
||||
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import java.lang.reflect.Method
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
@ -18,21 +17,6 @@ class ZonedDateTimeSerializer(
|
||||
ZonedDateTimeProxy::class.java,
|
||||
factory
|
||||
) {
|
||||
// Java deserialization of `ZonedDateTime` uses a private method. We will resolve this somewhat statically
|
||||
// so that any change to internals of `ZonedDateTime` is detected early.
|
||||
companion object {
|
||||
val ofLenient: Method = ZonedDateTime::class.java.getDeclaredMethod(
|
||||
"ofLenient",
|
||||
LocalDateTime::class.java,
|
||||
ZoneOffset::class.java,
|
||||
ZoneId::class.java
|
||||
)
|
||||
|
||||
init {
|
||||
ofLenient.isAccessible = true
|
||||
}
|
||||
}
|
||||
|
||||
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(
|
||||
LocalDateTimeSerializer(factory),
|
||||
ZoneIdSerializer(factory)
|
||||
@ -40,12 +24,7 @@ class ZonedDateTimeSerializer(
|
||||
|
||||
override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone)
|
||||
|
||||
override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ofLenient.invoke(
|
||||
null,
|
||||
proxy.dateTime,
|
||||
proxy.offset,
|
||||
proxy.zone
|
||||
) as ZonedDateTime
|
||||
override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ZonedDateTime.ofLocal(proxy.dateTime, proxy.zone, proxy.offset)
|
||||
|
||||
data class ZonedDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset, val zone: ZoneId)
|
||||
}
|
@ -2,18 +2,25 @@ package net.corda.serialization.internal.model
|
||||
|
||||
import net.corda.core.internal.isAbstractClass
|
||||
import net.corda.core.internal.isConcreteClass
|
||||
import net.corda.core.internal.isJdkClass
|
||||
import net.corda.core.internal.kotlinObjectInstance
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.serialization.internal.NotSerializableDetailedException
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.serialization.internal.amqp.PropertyDescriptor
|
||||
import net.corda.serialization.internal.amqp.TransformsAnnotationProcessor
|
||||
import net.corda.serialization.internal.amqp.asClass
|
||||
import net.corda.serialization.internal.amqp.calculatedPropertyDescriptors
|
||||
import net.corda.serialization.internal.amqp.componentType
|
||||
import net.corda.serialization.internal.amqp.propertyDescriptors
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.ACollection
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AMap
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Abstract
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnArray
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnEnum
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnInterface
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Atomic
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.ACollection
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AMap
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Composable
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Cycle
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.NonComposable
|
||||
@ -22,11 +29,12 @@ import net.corda.serialization.internal.model.LocalTypeInformation.Singleton
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Top
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Unknown
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.InaccessibleObjectException
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.collections.LinkedHashMap
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
@ -298,7 +306,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map<PropertyName, LocalPropertyInformation>): Boolean {
|
||||
if (!constructorInformation.hasParameters) return true
|
||||
|
||||
val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) {
|
||||
val indicesAddressedByProperties = properties.values.mapNotNullTo(LinkedHashSet()) {
|
||||
when (it) {
|
||||
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
@ -317,7 +325,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
): List<LocalConstructorParameterInformation> {
|
||||
if (!constructorInformation.hasParameters) return emptyList()
|
||||
|
||||
val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) {
|
||||
val indicesAddressedByProperties = properties.values.mapNotNullTo(LinkedHashSet()) {
|
||||
when (it) {
|
||||
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
@ -520,8 +528,7 @@ private fun constructorForDeserialization(type: Type): KFunction<Any>? {
|
||||
val defaultCtor = kotlinCtors.firstOrNull { it.parameters.isEmpty() }
|
||||
val nonDefaultCtors = kotlinCtors.filter { it != defaultCtor }
|
||||
|
||||
val preferredCandidate = clazz.kotlin.primaryConstructor ?:
|
||||
when(nonDefaultCtors.size) {
|
||||
val preferredCandidate = clazz.kotlin.primaryConstructor ?: when (nonDefaultCtors.size) {
|
||||
1 -> nonDefaultCtors.first()
|
||||
0 -> defaultCtor
|
||||
else -> null
|
||||
@ -531,6 +538,19 @@ private fun constructorForDeserialization(type: Type): KFunction<Any>? {
|
||||
preferredCandidate.apply { isAccessible = true }
|
||||
} catch (e: SecurityException) {
|
||||
null
|
||||
} catch (e: InaccessibleObjectException) {
|
||||
if (!clazz.isJdkClass || preferredCandidate.visibility == KVisibility.PUBLIC) {
|
||||
// We shouldn't be using private JDK constructors. For non-JDK classes, then re-throw as the client may need to open up that
|
||||
// module to us. Also throw if we can't get access to a public JDK constructor, which can probably happen if the class is not
|
||||
// exported (i.e. internal API).
|
||||
throw e
|
||||
}
|
||||
with(loggerFor<LocalTypeInformationBuilder>()) {
|
||||
if (isTraceEnabled) {
|
||||
trace("Ignoring private JDK constructor", e)
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package net.corda.serialization.internal.amqp
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Test
|
||||
import java.io.NotSerializableException
|
||||
@ -86,8 +86,9 @@ class DeserializeMapTests {
|
||||
val c = C(v)
|
||||
|
||||
// expected to throw
|
||||
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.")
|
||||
assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
.isInstanceOf(NotSerializableException::class.java)
|
||||
.hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.")
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
@ -100,7 +101,7 @@ class DeserializeMapTests {
|
||||
val c = C(v)
|
||||
|
||||
// expected to throw
|
||||
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Hashtable. Suggested fix: prefer java.util.map implementations")
|
||||
}
|
||||
|
||||
@ -111,7 +112,7 @@ class DeserializeMapTests {
|
||||
val c = C(HashMap(mapOf("A" to 1, "B" to 2)))
|
||||
|
||||
// expect this to throw
|
||||
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
|
||||
}
|
||||
|
||||
@ -121,7 +122,7 @@ class DeserializeMapTests {
|
||||
|
||||
val c = C(WeakHashMap(mapOf("A" to 1, "B" to 2)))
|
||||
|
||||
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
|
||||
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Weak references with map types not supported. Suggested fix: use java.util.LinkedHashMap instead.")
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,6 @@ include 'client:jfx'
|
||||
include 'client:mock'
|
||||
include 'client:rpc'
|
||||
include 'docker'
|
||||
include 'testing:client-rpc'
|
||||
include 'testing:testserver'
|
||||
include 'testing:testserver:testcapsule:'
|
||||
include 'experimental'
|
||||
|
@ -1,73 +0,0 @@
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
configurations {
|
||||
smokeTestImplementation.extendsFrom compile
|
||||
smokeTestRuntimeOnly.extendsFrom runtimeOnly
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
smokeTest {
|
||||
kotlin {
|
||||
// We must NOT have any Node code on the classpath, so do NOT
|
||||
// include the test or integrationTest dependencies here.
|
||||
compileClasspath += main.output
|
||||
runtimeClasspath += main.output
|
||||
srcDir file('src/smoke-test/kotlin')
|
||||
}
|
||||
java {
|
||||
compileClasspath += main.output
|
||||
runtimeClasspath += main.output
|
||||
srcDir file('src/smoke-test/java')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processSmokeTestResources {
|
||||
// Bring in the fully built corda.jar for use by NodeFactory in the smoke tests
|
||||
from(project(":node:capsule").tasks['buildCordaJAR']) {
|
||||
rename 'corda-(.*)', 'corda.jar'
|
||||
}
|
||||
from(project(':finance:workflows').tasks['jar']) {
|
||||
rename '.*finance-workflows-.*', 'cordapp-finance-workflows.jar'
|
||||
}
|
||||
from(project(':finance:contracts').tasks['jar']) {
|
||||
rename '.*finance-contracts-.*', 'cordapp-finance-contracts.jar'
|
||||
}
|
||||
from(project(':testing:cordapps:sleeping').tasks['jar']) {
|
||||
rename 'testing-sleeping-cordapp-*', 'cordapp-sleeping.jar'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Smoke tests do NOT have any Node code on the classpath!
|
||||
smokeTestImplementation project(':core')
|
||||
smokeTestImplementation project(':client:rpc')
|
||||
smokeTestImplementation project(':node-api')
|
||||
smokeTestImplementation project(':smoke-test-utils')
|
||||
smokeTestImplementation project(':finance:contracts')
|
||||
smokeTestImplementation project(':finance:workflows')
|
||||
smokeTestImplementation project(':testing:cordapps:sleeping')
|
||||
smokeTestImplementation "io.reactivex:rxjava:$rxjava_version"
|
||||
smokeTestImplementation "commons-io:commons-io:$commons_io_version"
|
||||
smokeTestImplementation "org.hamcrest:hamcrest-library:2.1"
|
||||
smokeTestImplementation "com.google.guava:guava-testlib:$guava_version"
|
||||
smokeTestImplementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
smokeTestImplementation "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
smokeTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
smokeTestImplementation "org.assertj:assertj-core:${assertj_version}"
|
||||
smokeTestImplementation "junit:junit:$junit_version"
|
||||
|
||||
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
|
||||
|
||||
// JDK11: required by Quasar at run-time
|
||||
smokeTestRuntimeOnly "com.esotericsoftware:kryo:$kryo_version"
|
||||
}
|
||||
|
||||
task smokeTest(type: Test) {
|
||||
testClassesDirs = sourceSets.smokeTest.output.classesDirs
|
||||
classpath = sourceSets.smokeTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
@ -102,17 +102,20 @@ dependencies {
|
||||
compileJava {
|
||||
doFirst {
|
||||
options.compilerArgs = [
|
||||
'--add-exports', 'java.base/sun.nio.ch=ALL-UNNAMED'
|
||||
'--add-modules', 'jdk.incubator.foreign'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
from(project(":node:capsule").files("src/main/resources/node-jvm-args.txt")) {
|
||||
into("net/corda/testing/node/internal")
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -1,10 +1,14 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
|
||||
import net.corda.testing.node.internal.nodeJvmArgs
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MockNetworkIntegrationTests {
|
||||
companion object {
|
||||
@ -22,16 +26,16 @@ class MockNetworkIntegrationTests {
|
||||
fun `does not leak non-daemon threads`() {
|
||||
val quasar = projectRootDir / "lib" / "quasar.jar"
|
||||
val quasarOptions = "m"
|
||||
val moduleOpens = listOf(
|
||||
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
|
||||
)
|
||||
|
||||
assertEquals(0, startJavaProcess<MockNetworkIntegrationTests>(emptyList(),
|
||||
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens).waitFor())
|
||||
val workingDirectory = Path("build", "MockNetworkIntegrationTests").apply {
|
||||
deleteRecursively()
|
||||
createDirectories()
|
||||
}
|
||||
val process = startJavaProcess<MockNetworkIntegrationTests>(
|
||||
emptyList(),
|
||||
workingDirectory = workingDirectory,
|
||||
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
|
||||
)
|
||||
assertThat(process.waitFor()).isZero()
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.stream.Collectors
|
||||
|
||||
|
||||
@RunWith(value = Parameterized::class)
|
||||
class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) {
|
||||
|
||||
@ -31,10 +30,7 @@ class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputR
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() {
|
||||
val process = ProcessUtilities.startJavaProcess(
|
||||
className = className,
|
||||
arguments = arguments,
|
||||
inheritIO = false)
|
||||
val process = ProcessUtilities.startJavaProcess(className = className, arguments = arguments)
|
||||
|
||||
process.waitFor()
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.internal.deleteRecursively
|
||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class InternalMockNetworkIntegrationTests {
|
||||
companion object {
|
||||
@ -22,17 +25,16 @@ class InternalMockNetworkIntegrationTests {
|
||||
fun `does not leak non-daemon threads`() {
|
||||
val quasar = projectRootDir / "lib" / "quasar.jar"
|
||||
val quasarOptions = "m"
|
||||
val moduleOpens = listOf(
|
||||
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
|
||||
)
|
||||
|
||||
assertEquals(0, startJavaProcess<InternalMockNetworkIntegrationTests>(emptyList(),
|
||||
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens
|
||||
).waitFor())
|
||||
val workingDirectory = Path("build", "InternalMockNetworkIntegrationTests").apply {
|
||||
deleteRecursively()
|
||||
createDirectories()
|
||||
}
|
||||
val process = startJavaProcess<InternalMockNetworkIntegrationTests>(
|
||||
emptyList(),
|
||||
workingDirectory = workingDirectory,
|
||||
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
|
||||
)
|
||||
assertThat(process.waitFor()).isZero()
|
||||
}
|
||||
}
|
||||
|
@ -1,97 +1,108 @@
|
||||
package net.corda.testing.driver;
|
||||
|
||||
import sun.misc.Unsafe;
|
||||
import sun.nio.ch.DirectBuffer;
|
||||
import jdk.incubator.foreign.MemoryHandles;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.reflect.Field;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler):
|
||||
* import sun.misc.Unsafe;
|
||||
* import sun.nio.ch.DirectBuffer;
|
||||
*/
|
||||
import static java.nio.file.StandardOpenOption.READ;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
// This was originally (re)written in Java to access internal JDK APIs. Since it's no longer doing that, this can be converted back to Kotlin.
|
||||
public class SharedMemoryIncremental extends PortAllocation {
|
||||
private static final int DEFAULT_START_PORT = 10_000;
|
||||
private static final int FIRST_EPHEMERAL_PORT = 30_000;
|
||||
|
||||
static private final int DEFAULT_START_PORT = 10_000;
|
||||
static private final int FIRST_EPHEMERAL_PORT = 30_000;
|
||||
private final int startPort;
|
||||
private final int endPort;
|
||||
|
||||
private int startPort;
|
||||
private int endPort;
|
||||
|
||||
private MappedByteBuffer mb;
|
||||
private Long startingAddress;
|
||||
|
||||
private File file = new File(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
|
||||
private RandomAccessFile backingFile;
|
||||
{
|
||||
try {
|
||||
backingFile = new RandomAccessFile(file, "rw");
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private final MemorySegment memorySegment;
|
||||
private final VarHandle intHandle;
|
||||
private final MappedByteBuffer unsafeBuffer;
|
||||
|
||||
private SharedMemoryIncremental(int startPort, int endPort) {
|
||||
this.startPort = startPort;
|
||||
this.endPort = endPort;
|
||||
Path file = Path.of(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
|
||||
try {
|
||||
mb = backingFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 16);
|
||||
startingAddress = ((DirectBuffer) mb).address();
|
||||
try {
|
||||
Files.createFile(file);
|
||||
} catch (FileAlreadyExistsException ignored) {}
|
||||
if (isFfmAvailable()) {
|
||||
memorySegment = MemorySegment.mapFile(file, 0, Integer.SIZE, MapMode.READ_WRITE, ResourceScope.globalScope());
|
||||
intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
|
||||
unsafeBuffer = null;
|
||||
} else {
|
||||
LoggerFactory.getLogger(getClass()).warn("Using unsafe port allocator which may lead to the same port being allocated " +
|
||||
"twice. Consider adding --add-modules=jdk.incubator.foreign to the test JVM.");
|
||||
memorySegment = null;
|
||||
intHandle = null;
|
||||
unsafeBuffer = FileChannel.open(file, READ, WRITE).map(MapMode.READ_WRITE, 0, Integer.SIZE);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFfmAvailable() {
|
||||
try {
|
||||
Class.forName("jdk.incubator.foreign.MemorySegment");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static SharedMemoryIncremental INSTANCE = new SharedMemoryIncremental(DEFAULT_START_PORT, FIRST_EPHEMERAL_PORT);
|
||||
static private Unsafe UNSAFE = getUnsafe();
|
||||
|
||||
static private Unsafe getUnsafe() {
|
||||
try {
|
||||
Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
f.setAccessible(true);
|
||||
return (Unsafe) f.get(null);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextPort() {
|
||||
long oldValue;
|
||||
long newValue;
|
||||
boolean loopSuccess;
|
||||
do {
|
||||
oldValue = UNSAFE.getLongVolatile(null, startingAddress);
|
||||
while (true) {
|
||||
int oldValue;
|
||||
if (intHandle != null) {
|
||||
oldValue = (int) intHandle.getVolatile(memorySegment, 0L);
|
||||
} else {
|
||||
oldValue = unsafeBuffer.getInt(0);
|
||||
}
|
||||
int newValue;
|
||||
if (oldValue + 1 >= endPort || oldValue < startPort) {
|
||||
newValue = startPort;
|
||||
} else {
|
||||
newValue = (oldValue + 1);
|
||||
}
|
||||
boolean reserveSuccess = UNSAFE.compareAndSwapLong(null, startingAddress, oldValue, newValue);
|
||||
loopSuccess = reserveSuccess && isLocalPortAvailable(newValue);
|
||||
} while (!loopSuccess);
|
||||
|
||||
return (int) newValue;
|
||||
if (intHandle != null) {
|
||||
if (!intHandle.compareAndSet(memorySegment, 0L, oldValue, newValue)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
unsafeBuffer.putInt(0, newValue);
|
||||
}
|
||||
if (isLocalPortAvailable(newValue)) {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isLocalPortAvailable(Long portToTest) {
|
||||
try (ServerSocket serverSocket = new ServerSocket(Math.toIntExact(portToTest))) {
|
||||
private boolean isLocalPortAvailable(int portToTest) {
|
||||
try (ServerSocket ignored = new ServerSocket(portToTest)) {
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// Don't catch anything other than IOException here in case we
|
||||
// accidentally create an infinite loop. For example, installing
|
||||
// a SecurityManager could throw AccessControlException.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.coretesting.internal.DEV_ROOT_CA
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
@ -76,7 +77,6 @@ import net.corda.testing.internal.MockCordappProvider
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.configureDatabase
|
||||
import net.corda.testing.internal.services.InternalMockAttachmentStorage
|
||||
import net.corda.testing.node.internal.DriverDSLImpl
|
||||
import net.corda.testing.node.internal.MockCryptoService
|
||||
import net.corda.testing.node.internal.MockKeyManagementService
|
||||
import net.corda.testing.node.internal.MockNetworkParametersStorage
|
||||
@ -85,6 +85,7 @@ import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.getCallerPackage
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.sql.Connection
|
||||
@ -141,8 +142,8 @@ open class MockServices private constructor(
|
||||
val dbPath = dbDir.resolve("persistence")
|
||||
try {
|
||||
DatabaseSnapshot.copyDatabaseSnapshot(dbDir)
|
||||
} catch (ex: java.nio.file.FileAlreadyExistsException) {
|
||||
DriverDSLImpl.log.warn("Database already exists on disk, not attempting to pre-migrate database.")
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
loggerFor<MockServices>().warn("Database already exists on disk, not attempting to pre-migrate database.")
|
||||
}
|
||||
val props = Properties()
|
||||
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
|
||||
|
@ -45,6 +45,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
||||
@ -845,7 +846,7 @@ class DriverDSLImpl(
|
||||
|
||||
companion object {
|
||||
private val RPC_CONNECT_POLL_INTERVAL: Duration = 100.millis
|
||||
internal val log = contextLogger()
|
||||
private val log = contextLogger()
|
||||
|
||||
// While starting with inProcess mode, we need to have different names to avoid clashes
|
||||
private val inMemoryCounter = AtomicInteger()
|
||||
@ -960,7 +961,7 @@ class DriverDSLImpl(
|
||||
"org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" +
|
||||
"org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" +
|
||||
"com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;)"
|
||||
val excludeClassloaderPattern = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)"
|
||||
val excludeClassloaderPattern = "l(net.corda.core.serialization.internal.**)"
|
||||
val quasarOptions = "m"
|
||||
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
|
||||
"-javaagent:$quasarJarPath=$quasarOptions$excludePackagePattern$excludeClassloaderPattern"
|
||||
@ -1002,24 +1003,11 @@ class DriverDSLImpl(
|
||||
&& !cpPathEntry.isExcludedJar
|
||||
}
|
||||
|
||||
val moduleOpens = listOf(
|
||||
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
|
||||
)
|
||||
|
||||
val moduleExports = listOf(
|
||||
"--add-exports", "java.base/sun.nio.ch=ALL-UNNAMED"
|
||||
)
|
||||
|
||||
return ProcessUtilities.startJavaProcess(
|
||||
className = "net.corda.node.Corda", // cannot directly get class for this, so just use string
|
||||
arguments = arguments,
|
||||
jdwpPort = debugPort,
|
||||
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + moduleOpens + moduleExports + "-Dnet.corda.node.printErrorsToStdErr=true",
|
||||
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + nodeJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
|
||||
workingDirectory = config.corda.baseDirectory,
|
||||
maximumHeapSize = maximumHeapSize,
|
||||
classPath = cp,
|
||||
@ -1066,22 +1054,13 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
|
||||
val className = "net.corda.webserver.WebServer"
|
||||
val moduleOpens = listOf(
|
||||
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
|
||||
)
|
||||
|
||||
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
|
||||
return ProcessUtilities.startJavaProcess(
|
||||
className = className, // cannot directly get class for this, so just use string
|
||||
className = "net.corda.webserver.WebServer", // cannot directly get class for this, so just use string
|
||||
workingDirectory = handle.baseDirectory,
|
||||
arguments = listOf(BASE_DIR, handle.baseDirectory.toString()),
|
||||
jdwpPort = debugPort,
|
||||
extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") + moduleOpens +
|
||||
extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") +
|
||||
inheritFromParentProcess().map { "-D${it.first}=${it.second}" },
|
||||
maximumHeapSize = maximumHeapSize
|
||||
)
|
||||
@ -1101,12 +1080,11 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
private fun NodeHandleInternal.toWebServerConfig(): Config {
|
||||
|
||||
var config = ConfigFactory.empty()
|
||||
config += "webAddress" to webAddress.toString()
|
||||
config += "myLegalName" to configuration.myLegalName.toString()
|
||||
config += "rpcAddress" to configuration.rpcOptions.address.toString()
|
||||
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers")
|
||||
config += "rpcUsers" to configuration.rpcUsers.map { it.toConfig().root().unwrapped() }
|
||||
config += "useHTTPS" to useHTTPS
|
||||
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
|
||||
|
||||
@ -1276,7 +1254,7 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
driverDsl.start()
|
||||
return dsl(coerce(driverDsl))
|
||||
} catch (exception: Throwable) {
|
||||
DriverDSLImpl.log.error("Driver shutting down because of exception", exception)
|
||||
loggerFor<DriverDSL>().error("Driver shutting down because of exception", exception)
|
||||
throw exception
|
||||
} finally {
|
||||
driverDsl.shutdown()
|
||||
|
@ -301,6 +301,10 @@ fun DriverDSL.assertUncompletedCheckpoints(name: CordaX500Name, expected: Long)
|
||||
}
|
||||
}
|
||||
|
||||
val nodeJvmArgs: List<String> by lazy {
|
||||
DriverDSLImpl::class.java.getResourceAsStream("node-jvm-args.txt")!!.use { it.bufferedReader().readLines() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be used by Driver and MockNode.
|
||||
*/
|
||||
|
@ -39,7 +39,6 @@ object ProcessUtilities {
|
||||
maximumHeapSize: String? = null,
|
||||
identifier: String = "",
|
||||
environmentVariables: Map<String,String> = emptyMap(),
|
||||
inheritIO: Boolean = true
|
||||
): Process {
|
||||
val command = mutableListOf<String>().apply {
|
||||
add(javaPath)
|
||||
@ -50,7 +49,6 @@ object ProcessUtilities {
|
||||
addAll(arguments)
|
||||
}
|
||||
return ProcessBuilder(command).apply {
|
||||
if (inheritIO) inheritIO()
|
||||
environment().putAll(environmentVariables)
|
||||
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
|
||||
if (workingDirectory != null) {
|
||||
|
@ -148,7 +148,7 @@ class NodeProcess(
|
||||
|
||||
|
||||
private fun createSchema(nodeDir: Path){
|
||||
val process = startNode(nodeDir, arrayOf("run-migration-scripts", "--core-schemas", "--app-schemas"))
|
||||
val process = startNode(nodeDir, "run-migration-scripts", "--core-schemas", "--app-schemas")
|
||||
if (!process.waitFor(schemaCreationTimeOutSeconds, SECONDS)) {
|
||||
process.destroy()
|
||||
throw SchemaCreationTimedOutError(nodeDir)
|
||||
@ -158,14 +158,15 @@ class NodeProcess(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SpreadOperator")
|
||||
private fun startNode(nodeDir: Path, extraArgs: Array<String> = emptyArray()): Process {
|
||||
private fun startNode(nodeDir: Path, vararg extraArgs: String): Process {
|
||||
val command = arrayListOf(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString())
|
||||
command += extraArgs
|
||||
val now = formatter.format(Instant.now())
|
||||
val builder = ProcessBuilder()
|
||||
.command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString(), *extraArgs)
|
||||
.command(command)
|
||||
.directory(nodeDir.toFile())
|
||||
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
|
||||
|
||||
.redirectError((nodeDir / "$now-stderr.log").toFile())
|
||||
.redirectOutput((nodeDir / "$now-stdout.log").toFile())
|
||||
builder.environment().putAll(mapOf(
|
||||
"CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString()
|
||||
))
|
||||
|
@ -80,9 +80,6 @@ dependencies {
|
||||
tasks.register('integrationTest', Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
|
||||
jvmArgs test_add_opens
|
||||
jvmArgs test_add_exports
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -2,14 +2,22 @@
|
||||
// must also be in the default package. When using Kotlin there are a whole host of exceptions
|
||||
// trying to construct this from Capsule, so it is written in Java.
|
||||
|
||||
import com.typesafe.config.*;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import com.typesafe.config.ConfigParseOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import sun.misc.Signal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CordaWebserverCaplet extends Capsule {
|
||||
@ -37,11 +45,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
}
|
||||
}
|
||||
|
||||
File getConfigFile(List<String> args, String baseDir) {
|
||||
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
|
||||
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
|
||||
}
|
||||
|
||||
String getBaseDirectory(List<String> args) {
|
||||
String baseDir = getOptionMultiple(args, Arrays.asList("--base-directory", "-b"));
|
||||
return Paths.get((baseDir == null) ? "." : baseDir).toAbsolutePath().normalize().toString();
|
||||
@ -69,7 +72,7 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
}
|
||||
|
||||
if (arg.toLowerCase().startsWith(lowerCaseOption)) {
|
||||
if (arg.length() > option.length() && arg.substring(option.length(), option.length() + 1).equals("=")) {
|
||||
if (arg.length() > option.length() && arg.charAt(option.length()) == '=') {
|
||||
return arg.substring(option.length() + 1);
|
||||
} else {
|
||||
return null;
|
||||
@ -87,23 +90,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
return super.prelaunch(jvmArgs, args);
|
||||
}
|
||||
|
||||
// Capsule does not handle multiple instances of same option hence we add in the args here to process builder
|
||||
// For multiple instances Capsule jvm args handling works on basis that one overrides the other.
|
||||
@Override
|
||||
protected int launch(ProcessBuilder pb) throws IOException, InterruptedException {
|
||||
if (isAtLeastJavaVersion11()) {
|
||||
List<String> args = pb.command();
|
||||
List<String> myArgs = Arrays.asList(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.time=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio=ALL-UNNAMED");
|
||||
args.addAll(1, myArgs);
|
||||
pb.command(args);
|
||||
}
|
||||
return super.launch(pb);
|
||||
}
|
||||
|
||||
// Add working directory variable to capsules string replacement variables.
|
||||
@Override
|
||||
protected String getVarValue(String var) {
|
||||
@ -157,9 +143,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
} catch (ConfigException e) {
|
||||
log(LOG_QUIET, e);
|
||||
}
|
||||
if (isAtLeastJavaVersion11()) {
|
||||
jvmArgs.add("-Dnashorn.args=--no-deprecation-warning");
|
||||
}
|
||||
return (T) jvmArgs;
|
||||
} else if (ATTR_SYSTEM_PROPERTIES == attr) {
|
||||
// Add system properties, if specified, from the config.
|
||||
@ -202,14 +185,6 @@ public class CordaWebserverCaplet extends Capsule {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAtLeastJavaVersion11() {
|
||||
String version = System.getProperty("java.specification.version");
|
||||
if (version != null) {
|
||||
return Float.parseFloat(version) >= 11f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean checkIfCordappDirExists(File dir) {
|
||||
try {
|
||||
if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case.
|
||||
|
@ -55,10 +55,6 @@ tasks.register('buildWebserverJar', FatCapsule) {
|
||||
// If you change these flags, please also update Driver.kt
|
||||
jvmArgs = ['-Xmx200m']
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver java.base/java.lang')
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
|
@ -16,20 +16,3 @@ dependencies {
|
||||
|
||||
runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes("Add-Opens":
|
||||
"java.base/java.lang " +
|
||||
"java.base/java.lang.reflect " +
|
||||
"java.base/java.lang.invoke " +
|
||||
"java.base/java.util " +
|
||||
"java.base/java.time " +
|
||||
"java.base/java.io " +
|
||||
"java.base/java.net " +
|
||||
"java.base/javax.net.ssl " +
|
||||
"java.base/java.security.cert " +
|
||||
"java.base/java.nio"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user