diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt index b7972dc2ec..b8715d867d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt @@ -3,40 +3,53 @@ package net.corda.nodeapi.internal import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import net.corda.core.contracts.Contract import net.corda.core.contracts.ContractClassName +import net.corda.core.contracts.UpgradedContract +import net.corda.core.contracts.UpgradedContractWithLegacyConstraint import net.corda.core.internal.copyTo import net.corda.core.internal.deleteIfExists +import net.corda.core.internal.logElapsedTime import net.corda.core.internal.read +import org.slf4j.LoggerFactory import java.io.InputStream import java.lang.reflect.Modifier import java.net.URLClassLoader import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths import java.nio.file.StandardCopyOption +import java.util.Collections.singleton + +// When scanning of the CorDapp Jar is performed without "corda-core.jar" being the in the classpath, there is no way to appreciate +// relationships between those interfaces, therefore they have to be listed explicitly. +val coreContractClasses = setOf(Contract::class, UpgradedContractWithLegacyConstraint::class, UpgradedContract::class) /** * Scans the jar for contracts. * @returns: found contract class names or null if none found */ fun scanJarForContracts(cordappJar: Path): List { - val currentClassLoader = Contract::class.java.classLoader val scanResult = FastClasspathScanner() - .addClassLoader(currentClassLoader) - .overrideClasspath(cordappJar, Paths.get(Contract::class.java.protectionDomain.codeSource.location.toURI())) + // A set of a single element may look odd, but if this is removed "Path" which itself is an `Iterable` + // is getting broken into pieces to scan individually, which doesn't yield desired effect. + .overrideClasspath(singleton(cordappJar)) .scan() - val contracts = (scanResult.getNamesOfClassesImplementing(Contract::class.qualifiedName) ).distinct() + val contracts = coreContractClasses.flatMap { contractClass -> scanResult.getNamesOfClassesImplementing(contractClass.qualifiedName) }.distinct() // Only keep instantiable contracts - return URLClassLoader(arrayOf(cordappJar.toUri().toURL()), currentClassLoader).use { + return URLClassLoader(arrayOf(cordappJar.toUri().toURL()), Contract::class.java.classLoader).use { contracts.map(it::loadClass).filter { !it.isInterface && !Modifier.isAbstract(it.modifiers) } }.map { it.name } } +private val logger = LoggerFactory.getLogger("ClassloaderUtils") + fun withContractsInJar(jarInputStream: InputStream, withContracts: (List, InputStream) -> T): T { val tempFile = Files.createTempFile("attachment", ".jar") try { jarInputStream.copyTo(tempFile, StandardCopyOption.REPLACE_EXISTING) - val contracts = scanJarForContracts(tempFile.toAbsolutePath()) + val cordappJar = tempFile.toAbsolutePath() + val contracts = logElapsedTime("Contracts loading for '$cordappJar'", logger) { + scanJarForContracts(cordappJar) + } return tempFile.read { withContracts(contracts, it) } } finally { tempFile.deleteIfExists() diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 5214ef0fb2..5cfd21b920 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -17,6 +17,7 @@ import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration +import net.corda.nodeapi.internal.coreContractClasses import net.corda.nodeapi.internal.serialization.DefaultWhitelist import org.apache.commons.collections4.map.LRUMap import java.io.File @@ -241,13 +242,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List { - return (scanResult.getNamesOfClassesImplementing(Contract::class) + - scanResult.getNamesOfClassesImplementing(UpgradedContract::class) + - // Even though UpgradedContractWithLegacyConstraint implements UpgradedContract - // we need to specify it separately. Otherwise, classes implementing UpgradedContractWithLegacyConstraint - // don't get picked up. - scanResult.getNamesOfClassesImplementing(UpgradedContractWithLegacyConstraint::class)) - .distinct() + return coreContractClasses.flatMap { scanResult.getNamesOfClassesImplementing(it) }.distinct() } private fun findPlugins(cordappJarPath: RestrictedURL): List {