diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 4c2445676a..78b6cd7ff0 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -52,13 +52,19 @@ data class LedgerTransaction @JvmOverloads constructor( private companion object { @JvmStatic - private fun createContractFor(className: ContractClassName): Try { - return Try.on { this::class.java.classLoader.loadClass(className).asSubclass(Contract::class.java).getConstructor().newInstance() } + private fun createContractFor(className: ContractClassName, classLoader: ClassLoader?): Try { + return Try.on { + (classLoader ?: this::class.java.classLoader) + .loadClass(className) + .asSubclass(Contract::class.java) + .getConstructor() + .newInstance() + } } } - private val contracts: Map> = (inputs.map { it.state.contract } + outputs.map { it.contract }) - .toSet().map { it to createContractFor(it) }.toMap() + private val contracts: Map> = (inputs.map { it.state } + outputs) + .map { it.contract to createContractFor(it.contract, it.data::class.java.classLoader) }.toMap() val inputStates: List get() = inputs.map { it.state.data } diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt index 2fc04a2d46..0684beacde 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt @@ -34,7 +34,7 @@ abstract class AbstractCashSelection { fun getInstance(metadata: () -> java.sql.DatabaseMetaData): AbstractCashSelection { return instance.get() ?: { val _metadata = metadata() - val cashSelectionAlgos = ServiceLoader.load(AbstractCashSelection::class.java).toList() + val cashSelectionAlgos = ServiceLoader.load(AbstractCashSelection::class.java, this::class.java.classLoader).toList() val cashSelectionAlgo = cashSelectionAlgos.firstOrNull { it.isCompatible(_metadata) } cashSelectionAlgo?.let { instance.set(cashSelectionAlgo) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 4884c21832..6c16d28785 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -52,7 +52,8 @@ class CordaPersistence( val dataSource: DataSource, databaseConfig: DatabaseConfig, schemas: Set, - attributeConverters: Collection> = emptySet() + attributeConverters: Collection> = emptySet(), + val cordappClassLoader: ClassLoader? = null ) : Closeable { companion object { private val log = contextLogger() @@ -61,7 +62,7 @@ class CordaPersistence( private val defaultIsolationLevel = databaseConfig.transactionIsolationLevel val hibernateConfig: HibernateConfiguration by lazy { transaction { - HibernateConfiguration(schemas, databaseConfig, attributeConverters) + HibernateConfiguration(schemas, databaseConfig, attributeConverters, cordappClassLoader) } } val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt index 29f6783388..ea3c11d252 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt @@ -9,6 +9,8 @@ import org.hibernate.boot.MetadataSources import org.hibernate.boot.model.naming.Identifier import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService import org.hibernate.cfg.Configuration import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment @@ -26,7 +28,8 @@ import javax.persistence.AttributeConverter class HibernateConfiguration( schemas: Set, private val databaseConfig: DatabaseConfig, - private val attributeConverters: Collection> + private val attributeConverters: Collection>, + val cordappClassLoader: ClassLoader? = null ) { companion object { private val logger = contextLogger() @@ -60,7 +63,7 @@ class HibernateConfiguration( schema.mappedTypes.forEach { config.addAnnotatedClass(it) } } - val sessionFactory = buildSessionFactory(config, metadataSources, databaseConfig.serverNameTablePrefix) + val sessionFactory = buildSessionFactory(config, metadataSources, databaseConfig.serverNameTablePrefix, cordappClassLoader) logger.info("Created session factory for schemas: $schemas") // export Hibernate JMX statistics @@ -87,8 +90,15 @@ class HibernateConfiguration( } } - private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources, tablePrefix: String): SessionFactory { + private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources, tablePrefix: String, cordappClassLoader: ClassLoader?): SessionFactory { config.standardServiceRegistryBuilder.applySettings(config.properties) + + if (cordappClassLoader != null) { + config.standardServiceRegistryBuilder.addService( + ClassLoaderService::class.java, + ClassLoaderServiceImpl(cordappClassLoader)) + } + val metadata = metadataSources.getMetadataBuilder(config.standardServiceRegistryBuilder.build()).run { applyPhysicalNamingStrategy(object : PhysicalNamingStrategyStandardImpl() { override fun toPhysicalTableName(name: Identifier?, context: JdbcEnvironment?): Identifier { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt index 138326dde6..31def96b4f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt @@ -18,7 +18,8 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *> * Serialization / deserialization of certain supported [Map] types. */ class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer { - override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) + override val type: Type = (declaredType as? DeserializedParameterizedType) ?: + DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType), factory.classloader) override val typeDescriptor: Symbol = Symbol.valueOf( "$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") diff --git a/node/src/main/java/CordaCaplet.java b/node/src/main/java/CordaCaplet.java index ac81f04b1c..c127eefb8c 100644 --- a/node/src/main/java/CordaCaplet.java +++ b/node/src/main/java/CordaCaplet.java @@ -81,8 +81,6 @@ public class CordaCaplet extends Capsule { T cp = super.attribute(attr); (new File(baseDir, "cordapps")).mkdir(); - augmentClasspath((List) cp, new File(baseDir, "cordapps")); - augmentClasspath((List) cp, new File(baseDir, "plugins")); // Add additional directories of JARs to the classpath (at the end). e.g. for JDBC drivers try { List jarDirs = nodeConfig.getStringList("jarDirs"); diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index eb2b936b5e..4d761a6ded 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -639,7 +639,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected open fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService): CordaPersistence { val props = configuration.dataSourceProperties if (props.isEmpty()) throw DatabaseConfigurationException("There must be a database configured.") - val database = configureDatabase(props, configuration.database, identityService, schemaService) + val database = configureDatabase(props, configuration.database, identityService, schemaService, cordappLoader.appClassLoader) // Now log the vendor string as this will also cause a connection to be tested eagerly. logVendorString(database, log) runOnStop += database::close @@ -874,7 +874,8 @@ internal class NetworkMapCacheEmptyException : Exception() fun configureDatabase(hikariProperties: Properties, databaseConfig: DatabaseConfig, identityService: IdentityService, - schemaService: SchemaService = NodeSchemaService()): CordaPersistence { + schemaService: SchemaService = NodeSchemaService(), + cordappClassLoader: ClassLoader? = null): CordaPersistence { // Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately // Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default // so we end up providing both descriptor and converter. We should re-examine this in later versions to see if @@ -882,5 +883,5 @@ fun configureDatabase(hikariProperties: Properties, JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService)) val dataSource = DataSourceFactory.createDataSource(hikariProperties) val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(identityService)) - return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, attributeConverters) + return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, attributeConverters, cordappClassLoader) } 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 98947dbe78..5214ef0fb2 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 @@ -43,8 +43,7 @@ import kotlin.streams.toList */ class CordappLoader private constructor(private val cordappJarPaths: List) { val cordapps: List by lazy { loadCordapps() + coreCordapp } - - internal val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) + val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) init { if (cordappJarPaths.isEmpty()) { diff --git a/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index bd694bd41b..a24edcff31 100644 --- a/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -160,7 +160,7 @@ object NodeInterestRates { } private fun addDefaultFixes() { - knownFixes = parseFile(IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt"), Charsets.UTF_8.name())) + knownFixes = parseFile(IOUtils.toString(this::class.java.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt"), Charsets.UTF_8.name())) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 7a2e3cba34..54d5fe3362 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -102,7 +102,7 @@ open class MockServices private constructor( val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) val dataSourceProps = makeTestDataSourceProperties() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) - val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, schemaService) + val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, schemaService, cordappLoader.appClassLoader) val mockService = database.transaction { object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) { override val vaultService: VaultService = makeVaultService(database.hibernateConfig, schemaService)