From a7d0b124d2474442c06e4834070484dd3eb32b5d Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 14 Mar 2018 13:38:21 +0000 Subject: [PATCH 1/4] Set up instructions no longer rely on Java installer in Mac (defaults to JDK 9). --- docs/source/getting-set-up.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index aef199da90..b8cd50727a 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -107,11 +107,12 @@ Mac Java ^^^^ -1. Open "System Preferences > Java" -2. In the Java Control Panel, if an update is available, click "Update Now" -3. In the "Software Update" window, click "Install Update". If required, enter your password and click "Install Helper" when prompted -4. Wait for a pop-up window indicating that you have successfully installed the update, and click "Close" -5. Open a new terminal and type ``java -version`` to test that Java is installed correctly +1. Visit http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html +2. Scroll down to "Java SE Development Kit 8uXXX" (where "XXX" is the latest minor version number) +3. Toggle "Accept License Agreement" +4. Click the download link for jdk-8uXXX-macosx-x64.dmg (where "XXX" is the latest minor version number) +5. Download and run the executable to install Java (use the default settings) +6. Open a new terminal window and run ``java -version`` to test that Java is installed correctly IntelliJ ^^^^^^^^ @@ -173,4 +174,4 @@ By then, you'll be ready to start writing your own CorDapps. Learn how to do thi :doc:`flow cookbook ` and the `samples `_ along the way. If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the -`forums `_ or via `slack `_. \ No newline at end of file +`forums `_ or via `slack `_. From 8f750c062945be26a200840898e0ecb85eadf4c3 Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Wed, 14 Mar 2018 13:42:18 +0000 Subject: [PATCH 2/4] CORDA-1042: Change the way how Jolokia library located on the classpath so that it work on Windows (#2817) Old code failed with: ``` 11:34:01.536 [main] ERROR net.corda.node.internal.Node - Exception during node startup java.nio.file.InvalidPathException: Illegal char <:> at index 2: /C:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202017.3.1/lib/idea_rt.jar at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182) ~[?:1.8.0_144] at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153) ~[?:1.8.0_144] at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77) ~[?:1.8.0_144] at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94) ~[?:1.8.0_144] at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255) ~[?:1.8.0_144] at java.nio.file.Paths.get(Paths.java:84) ~[?:1.8.0_144] at net.corda.node.utilities.JVMAgentRegistry.resolveAgentJar(JVMAgentRegistry.kt:46) ~[classes/:?] at net.corda.node.internal.AbstractNode.initialiseJVMAgents(AbstractNode.kt:785) ~[classes/:?] at net.corda.node.internal.AbstractNode.start(AbstractNode.kt:199) ~[classes/:?] at net.corda.node.internal.Node.start(Node.kt:335) ~[classes/:?] at net.corda.node.internal.NodeStartup.startNode(NodeStartup.kt:146) ~[classes/:?] at net.corda.node.internal.NodeStartup.run(NodeStartup.kt:120) [classes/:?] at net.corda.node.Corda.main(Corda.kt:16) [classes/:?] ``` --- .../main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt b/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt index 54850dee10..1b0c685cb9 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt @@ -43,7 +43,7 @@ object JVMAgentRegistry { } else { (this::class.java.classLoader as? URLClassLoader) ?.urLs - ?.map { Paths.get(it.path) } + ?.map { Paths.get(it.toURI()) } ?.firstOrNull { it.fileName.toString() == jarFileName } } } From a24a2105b193b999804a5bd5e2f38ae7fe18b7e8 Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Wed, 14 Mar 2018 16:07:31 +0000 Subject: [PATCH 3/4] CORDA-1217 Replace Guava caches with Caffeine (#2818) --- build.gradle | 1 + .../client/jfx/model/NetworkIdentityModel.kt | 9 +++-- client/rpc/build.gradle | 3 ++ .../corda/client/rpc/internal/RPCClient.kt | 7 ---- .../rpc/internal/RPCClientProxyHandler.kt | 20 +++++------ .../corda/client/rpc/RPCConcurrencyTests.kt | 3 +- .../corda/client/rpc/RPCPerformanceTests.kt | 4 +-- constants.properties | 1 + node-api/build.gradle | 3 ++ .../nodeapi/internal/DeduplicationChecker.kt | 11 ++++--- .../serialization/SerializationScheme.kt | 8 ++--- node/build.gradle | 3 ++ .../security/RPCSecurityManagerImpl.kt | 27 +++++++-------- .../node/services/messaging/RPCServer.kt | 15 +++++---- .../network/PersistentNetworkMapCache.kt | 8 ++--- .../persistence/NodeAttachmentService.kt | 9 ++--- .../node/utilities/AppendOnlyPersistentMap.kt | 12 +++---- .../node/utilities/NonInvalidatingCache.kt | 33 +++++++++---------- .../utilities/NonInvalidatingUnboundCache.kt | 20 ++++++----- .../net/corda/node/utilities/PersistentMap.kt | 24 +++++++------- .../explorer/identicon/IdenticonRenderer.kt | 8 ++--- 21 files changed, 112 insertions(+), 117 deletions(-) diff --git a/build.gradle b/build.gradle index 0d2d8ec9a4..1ee0c3a42a 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,7 @@ buildscript { ext.log4j_version = '2.9.1' ext.bouncycastle_version = constants.getProperty("bouncycastleVersion") ext.guava_version = constants.getProperty("guavaVersion") + ext.caffeine_version = constants.getProperty("caffeineVersion") ext.okhttp_version = '3.5.0' ext.netty_version = '4.1.9.Final' ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion") diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt index 5451f2acce..181fa895e6 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt @@ -1,7 +1,6 @@ package net.corda.client.jfx.model -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.collections.ObservableList @@ -32,8 +31,8 @@ class NetworkIdentityModel { private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable) - private val identityCache = CacheBuilder.newBuilder() - .build>(CacheLoader.from { publicKey -> + private val identityCache = Caffeine.newBuilder() + .build>({ publicKey -> publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } } }) val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.notaryIdentities() ?: emptyList()) }) @@ -42,5 +41,5 @@ class NetworkIdentityModel { .filtered { it.legalIdentities.all { it !in notaries } } val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party } - fun partyFromPublicKey(publicKey: PublicKey): ObservableValue = identityCache[publicKey] + fun partyFromPublicKey(publicKey: PublicKey): ObservableValue = identityCache[publicKey]!! } diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle index 3b585cf164..d60a6eb8d5 100644 --- a/client/rpc/build.gradle +++ b/client/rpc/build.gradle @@ -68,6 +68,9 @@ dependencies { compile project(':core') compile project(':node-api') + // For caches rather than guava + compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version" + // Unit testing helpers. testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "junit:junit:$junit_version" diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index 79ad14f5dd..26d027e287 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -44,12 +44,6 @@ data class RPCClientConfiguration( val reapInterval: Duration, /** The number of threads to use for observations (for executing [Observable.onNext]) */ val observationExecutorPoolSize: Int, - /** - * Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines - * the limit on the number of leaked observables reaped because of garbage collection per reaping. - * See the implementation of [com.google.common.cache.LocalCache] for details. - */ - val cacheConcurrencyLevel: Int, /** The retry interval of artemis connections in milliseconds */ val connectionRetryInterval: Duration, /** The retry interval multiplier for exponential backoff */ @@ -71,7 +65,6 @@ data class RPCClientConfiguration( trackRpcCallSites = false, reapInterval = 1.seconds, observationExecutorPoolSize = 4, - cacheConcurrencyLevel = 8, connectionRetryInterval = 5.seconds, connectionRetryIntervalMultiplier = 1.5, connectionMaxRetryInterval = 3.minutes, diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index 54b931ed8d..6964fe493d 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -1,13 +1,14 @@ package net.corda.client.rpc.internal +import co.paralleluniverse.common.util.SameThreadExecutor import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Serializer import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder -import com.google.common.cache.RemovalCause -import com.google.common.cache.RemovalListener +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.RemovalCause +import com.github.benmanes.caffeine.cache.RemovalListener import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.client.rpc.RPCException @@ -142,10 +143,10 @@ class RPCClientProxyHandler( private val serializationContextWithObservableContext = RpcClientObservableSerializer.createContext(serializationContext, observableContext) private fun createRpcObservableMap(): RpcObservableMap { - val onObservableRemove = RemovalListener>> { - val observableId = it.key!! + val onObservableRemove = RemovalListener>> { key, value, cause -> + val observableId = key!! val rpcCallSite = callSiteMap?.remove(observableId) - if (it.cause == RemovalCause.COLLECTED) { + if (cause == RemovalCause.COLLECTED) { log.warn(listOf( "A hot observable returned from an RPC was never subscribed to.", "This wastes server-side resources because it was queueing observations for retrieval.", @@ -156,10 +157,9 @@ class RPCClientProxyHandler( } observablesToReap.locked { observables.add(observableId) } } - return CacheBuilder.newBuilder(). + return Caffeine.newBuilder(). weakValues(). - removalListener(onObservableRemove). - concurrencyLevel(rpcConfiguration.cacheConcurrencyLevel). + removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()). build() } diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt index 5ea77a66fd..fe405b37af 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt @@ -91,8 +91,7 @@ class RPCConcurrencyTests : AbstractRPCTest() { return testProxy( TestOpsImpl(pool), clientConfiguration = RPCClientConfiguration.default.copy( - reapInterval = 100.millis, - cacheConcurrencyLevel = 16 + reapInterval = 100.millis ), serverConfiguration = RPCServerConfiguration.default.copy( rpcThreadPoolSize = 4 diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt index 689674d22f..0211d0b3ec 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt @@ -87,7 +87,6 @@ class RPCPerformanceTests : AbstractRPCTest() { rpcDriver { val proxy = testProxy( RPCClientConfiguration.default.copy( - cacheConcurrencyLevel = 16, observationExecutorPoolSize = 2 ), RPCServerConfiguration.default.copy( @@ -127,8 +126,7 @@ class RPCPerformanceTests : AbstractRPCTest() { val metricRegistry = startReporter(shutdownManager) val proxy = testProxy( RPCClientConfiguration.default.copy( - reapInterval = 1.seconds, - cacheConcurrencyLevel = 16 + reapInterval = 1.seconds ), RPCServerConfiguration.default.copy( rpcThreadPoolSize = 8 diff --git a/constants.properties b/constants.properties index 3f219493b2..134d656085 100644 --- a/constants.properties +++ b/constants.properties @@ -7,3 +7,4 @@ typesafeConfigVersion=1.3.1 jsr305Version=3.0.2 artifactoryPluginVersion=4.4.18 snakeYamlVersion=1.19 +caffeineVersion=2.6.2 diff --git a/node-api/build.gradle b/node-api/build.gradle index 6cc03bd117..405221b374 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -40,6 +40,9 @@ dependencies { // Pure-Java Snappy compression compile 'org.iq80.snappy:snappy:0.4' + // For caches rather than guava + compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version" + // Unit testing helpers. testCompile "junit:junit:$junit_version" testCompile "org.assertj:assertj-core:$assertj_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt index b35b8922a3..86742c6325 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt @@ -1,7 +1,7 @@ package net.corda.nodeapi.internal -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine import java.time.Duration import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong @@ -11,11 +11,11 @@ import java.util.concurrent.atomic.AtomicLong */ class DeduplicationChecker(cacheExpiry: Duration) { // dedupe identity -> watermark cache - private val watermarkCache = CacheBuilder.newBuilder() + private val watermarkCache = Caffeine.newBuilder() .expireAfterAccess(cacheExpiry.toNanos(), TimeUnit.NANOSECONDS) .build(WatermarkCacheLoader) - private object WatermarkCacheLoader : CacheLoader() { + private object WatermarkCacheLoader : CacheLoader { override fun load(key: Any) = AtomicLong(-1) } @@ -25,6 +25,7 @@ class DeduplicationChecker(cacheExpiry: Duration) { * @return true if the message is unique, false if it's a duplicate. */ fun checkDuplicateMessageId(identity: Any, sequenceNumber: Long): Boolean { - return watermarkCache[identity].getAndUpdate { maxOf(sequenceNumber, it) } >= sequenceNumber + return watermarkCache[identity]!!.getAndUpdate { maxOf(sequenceNumber, it) } >= sequenceNumber } } + diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index a093bc871b..369978bb62 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -1,7 +1,7 @@ package net.corda.nodeapi.internal.serialization -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.internal.copyBytes @@ -30,7 +30,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe override val useCase: SerializationContext.UseCase, override val encoding: SerializationEncoding?, override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext { - private val cache: Cache, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build() + private val cache: Cache, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build() /** * {@inheritDoc} @@ -49,7 +49,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe } missing.isNotEmpty() && throw MissingAttachmentsException(missing) AttachmentsClassLoader(attachments, parent = deserializationClassLoader) - }) + }!!) } catch (e: ExecutionException) { // Caught from within the cache get, so unwrap. throw e.cause!! diff --git a/node/build.gradle b/node/build.gradle index 74cad1b2ef..2158c98b2c 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -79,6 +79,9 @@ dependencies { compile "com.google.guava:guava:$guava_version" + // For caches rather than guava + compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version" + // JOpt: for command line flags. compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt index 4b1daabdde..eec5d8ff33 100644 --- a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt @@ -1,7 +1,8 @@ package net.corda.node.internal.security -import com.google.common.cache.CacheBuilder -import com.google.common.cache.Cache + +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine import com.google.common.primitives.Ints import net.corda.core.context.AuthServiceId import net.corda.core.utilities.loggerFor @@ -94,7 +95,7 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager { return DefaultSecurityManager(realm).also { // Setup optional cache layer if configured it.cacheManager = config.options?.cache?.let { - GuavaCacheManager( + CaffeineCacheManager( timeToLiveSeconds = it.expireAfterSecs, maxSize = it.maxEntries) } @@ -257,9 +258,9 @@ private class NodeJdbcRealm(config: SecurityConfiguration.AuthService.DataSource private typealias ShiroCache = org.apache.shiro.cache.Cache /* - * Adapts a [com.google.common.cache.Cache] to a [org.apache.shiro.cache.Cache] implementation. + * Adapts a [com.github.benmanes.caffeine.cache.Cache] to a [org.apache.shiro.cache.Cache] implementation. */ -private fun Cache.toShiroCache(name: String) = object : ShiroCache { +private fun Cache.toShiroCache(name: String) = object : ShiroCache { val name = name private val impl = this@toShiroCache @@ -282,7 +283,7 @@ private fun Cache.toShiroCache(name: String) = object : ShiroCache< impl.invalidateAll() } - override fun size() = Ints.checkedCast(impl.size()) + override fun size() = Ints.checkedCast(impl.estimatedSize()) override fun keys() = impl.asMap().keys override fun values() = impl.asMap().values override fun toString() = "Guava cache adapter [$impl]" @@ -290,22 +291,22 @@ private fun Cache.toShiroCache(name: String) = object : ShiroCache< /* * Implementation of [org.apache.shiro.cache.CacheManager] based on - * cache implementation in [com.google.common.cache] + * cache implementation in [com.github.benmanes.caffeine.cache.Cache] */ -private class GuavaCacheManager(val maxSize: Long, - val timeToLiveSeconds: Long) : CacheManager { +private class CaffeineCacheManager(val maxSize: Long, + val timeToLiveSeconds: Long) : CacheManager { private val instances = ConcurrentHashMap>() - override fun getCache(name: String): ShiroCache { + override fun getCache(name: String): ShiroCache { val result = instances[name] ?: buildCache(name) instances.putIfAbsent(name, result) return result as ShiroCache } - private fun buildCache(name: String) : ShiroCache { + private fun buildCache(name: String): ShiroCache { logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s") - return CacheBuilder.newBuilder() + return Caffeine.newBuilder() .expireAfterWrite(timeToLiveSeconds, TimeUnit.SECONDS) .maximumSize(maxSize) .build() @@ -313,6 +314,6 @@ private class GuavaCacheManager(val maxSize: Long, } companion object { - private val logger = loggerFor() + private val logger = loggerFor() } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 657de9535e..e70f80d39d 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -1,12 +1,13 @@ package net.corda.node.services.messaging +import co.paralleluniverse.common.util.SameThreadExecutor import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Serializer import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder -import com.google.common.cache.RemovalListener +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.RemovalListener import com.google.common.collect.HashMultimap import com.google.common.collect.Multimaps import com.google.common.collect.SetMultimap @@ -145,11 +146,11 @@ class RPCServer( } private fun createObservableSubscriptionMap(): ObservableSubscriptionMap { - val onObservableRemove = RemovalListener { - log.debug { "Unsubscribing from Observable with id ${it.key} because of ${it.cause}" } - it.value.subscription.unsubscribe() + val onObservableRemove = RemovalListener { key, value, cause -> + log.debug { "Unsubscribing from Observable with id ${key} because of ${cause}" } + value!!.subscription.unsubscribe() } - return CacheBuilder.newBuilder().removalListener(onObservableRemove).build() + return Caffeine.newBuilder().removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()).build() } fun start(activeMqServerControl: ActiveMQServerControl) { diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 19fb9fd875..caefa2d3d6 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -164,9 +164,9 @@ open class PersistentNetworkMapCache( override fun getNodesByLegalName(name: CordaX500Name): List = database.transaction { queryByLegalName(session, name) } - override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = nodesByKeyCache[identityKey] + override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = nodesByKeyCache[identityKey]!! - private val nodesByKeyCache = NonInvalidatingCache>(1024, 8, { key -> database.transaction { queryByIdentityKey(session, key) } }) + private val nodesByKeyCache = NonInvalidatingCache>(1024, { key -> database.transaction { queryByIdentityKey(session, key) } }) override fun getNodesByOwningKeyIndex(identityKeyIndex: String): List { return database.transaction { @@ -176,9 +176,9 @@ open class PersistentNetworkMapCache( override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) } - override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = identityByLegalNameCache.get(name).orElse(null) + override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = identityByLegalNameCache.get(name)!!.orElse(null) - private val identityByLegalNameCache = NonInvalidatingCache>(1024, 8, { name -> Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) }) }) + private val identityByLegalNameCache = NonInvalidatingCache>(1024, { name -> Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) }) }) override fun track(): DataFeed, MapChange> { synchronized(_changed) { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 9593cfc634..e4ea68a1e6 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.persistence import com.codahale.metrics.MetricRegistry -import com.google.common.cache.Weigher +import com.github.benmanes.caffeine.cache.Weigher import com.google.common.hash.HashCode import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream @@ -24,7 +24,6 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser import net.corda.node.utilities.NonInvalidatingCache import net.corda.node.utilities.NonInvalidatingWeightBasedCache -import net.corda.node.utilities.defaultCordaCacheConcurrencyLevel import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.withContractsInJar @@ -209,7 +208,6 @@ class NodeAttachmentService( private val attachmentContentCache = NonInvalidatingWeightBasedCache>>( maxWeight = attachmentContentCacheSize, - concurrencyLevel = defaultCordaCacheConcurrencyLevel, weigher = object : Weigher>> { override fun weigh(key: SecureHash, value: Optional>): Int { return key.size + if (value.isPresent) value.get().second.size else 0 @@ -234,12 +232,11 @@ class NodeAttachmentService( private val attachmentCache = NonInvalidatingCache>( attachmentCacheBound, - defaultCordaCacheConcurrencyLevel, { key -> Optional.ofNullable(createAttachment(key)) } ) private fun createAttachment(key: SecureHash): Attachment? { - val content = attachmentContentCache.get(key) + val content = attachmentContentCache.get(key)!! if (content.isPresent) { return content.get().first } @@ -249,7 +246,7 @@ class NodeAttachmentService( } override fun openAttachment(id: SecureHash): Attachment? { - val attachment = attachmentCache.get(id) + val attachment = attachmentCache.get(id)!! if (attachment.isPresent) { return attachment.get() } diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index c6b875dbd9..ad3df17747 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -1,7 +1,7 @@ package net.corda.node.utilities -import com.google.common.cache.LoadingCache -import com.google.common.cache.Weigher +import com.github.benmanes.caffeine.cache.LoadingCache +import com.github.benmanes.caffeine.cache.Weigher import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.currentDBSession import java.util.* @@ -29,7 +29,7 @@ abstract class AppendOnlyPersistentMapBase( * Returns the value associated with the key, first loading that value from the storage if necessary. */ operator fun get(key: K): V? { - return cache.get(key).orElse(null) + return cache.get(key)!!.orElse(null) } val size get() = allPersisted().toList().size @@ -62,7 +62,7 @@ abstract class AppendOnlyPersistentMapBase( } else { Optional.of(value) } - } + }!! if (!insertionAttempt) { if (existingInCache.isPresent) { // Key already exists in cache, do nothing. @@ -71,7 +71,7 @@ abstract class AppendOnlyPersistentMapBase( // This happens when the key was queried before with no value associated. We invalidate the cached null // value and recursively call set again. This is to avoid race conditions where another thread queries after // the invalidate but before the set. - cache.invalidate(key) + cache.invalidate(key!!) return set(key, value, logWarning, store) } } @@ -148,7 +148,6 @@ class AppendOnlyPersistentMap( //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size override val cache = NonInvalidatingCache>( bound = cacheBound, - concurrencyLevel = 8, loadFunction = { key -> Optional.ofNullable(loadValue(key)) }) } @@ -166,7 +165,6 @@ class WeightBasedAppendOnlyPersistentMap( persistentEntityClass) { override val cache = NonInvalidatingWeightBasedCache>( maxWeight = maxWeight, - concurrencyLevel = 8, weigher = object : Weigher> { override fun weigh(key: K, value: Optional): Int { return weighingFunc(key, value) diff --git a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt index f08a0631cb..d8840bacc2 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt @@ -1,29 +1,28 @@ package net.corda.node.utilities -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader -import com.google.common.cache.LoadingCache -import com.google.common.cache.Weigher -import com.google.common.util.concurrent.ListenableFuture +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.LoadingCache +import com.github.benmanes.caffeine.cache.Weigher class NonInvalidatingCache private constructor( val cache: LoadingCache ) : LoadingCache by cache { - constructor(bound: Long, concurrencyLevel: Int, loadFunction: (K) -> V) : - this(buildCache(bound, concurrencyLevel, loadFunction)) + constructor(bound: Long, loadFunction: (K) -> V) : + this(buildCache(bound, loadFunction)) private companion object { - private fun buildCache(bound: Long, concurrencyLevel: Int, loadFunction: (K) -> V): LoadingCache { - val builder = CacheBuilder.newBuilder().maximumSize(bound).concurrencyLevel(concurrencyLevel) + private fun buildCache(bound: Long, loadFunction: (K) -> V): LoadingCache { + val builder = Caffeine.newBuilder().maximumSize(bound) return builder.build(NonInvalidatingCacheLoader(loadFunction)) } } // TODO look into overriding loadAll() if we ever use it - class NonInvalidatingCacheLoader(val loadFunction: (K) -> V) : CacheLoader() { - override fun reload(key: K, oldValue: V): ListenableFuture { + class NonInvalidatingCacheLoader(val loadFunction: (K) -> V) : CacheLoader { + override fun reload(key: K, oldValue: V): V { throw IllegalStateException("Non invalidating cache refreshed") } @@ -34,16 +33,14 @@ class NonInvalidatingCache private constructor( class NonInvalidatingWeightBasedCache private constructor( val cache: LoadingCache ) : LoadingCache by cache { - constructor (maxWeight: Long, concurrencyLevel: Int, weigher: Weigher, loadFunction: (K) -> V) : - this(buildCache(maxWeight, concurrencyLevel, weigher, loadFunction)) + constructor (maxWeight: Long, weigher: Weigher, loadFunction: (K) -> V) : + this(buildCache(maxWeight, weigher, loadFunction)) private companion object { - private fun buildCache(maxWeight: Long, concurrencyLevel: Int, weigher: Weigher, loadFunction: (K) -> V): LoadingCache { - val builder = CacheBuilder.newBuilder().maximumWeight(maxWeight).weigher(weigher).concurrencyLevel(concurrencyLevel) + private fun buildCache(maxWeight: Long, weigher: Weigher, loadFunction: (K) -> V): LoadingCache { + val builder = Caffeine.newBuilder().maximumWeight(maxWeight).weigher(weigher) return builder.build(NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction)) } } -} - -val defaultCordaCacheConcurrencyLevel: Int = 8 \ No newline at end of file +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingUnboundCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingUnboundCache.kt index ea16bfd064..b1fef1d2e6 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingUnboundCache.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingUnboundCache.kt @@ -1,21 +1,23 @@ package net.corda.node.utilities -import com.google.common.cache.* -import com.google.common.util.concurrent.ListenableFuture - +import co.paralleluniverse.common.util.SameThreadExecutor +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.LoadingCache +import com.github.benmanes.caffeine.cache.RemovalListener class NonInvalidatingUnboundCache private constructor( val cache: LoadingCache ) : LoadingCache by cache { - constructor(concurrencyLevel: Int, loadFunction: (K) -> V, removalListener: RemovalListener = RemovalListener {}, + constructor(loadFunction: (K) -> V, removalListener: RemovalListener = RemovalListener { key, value, cause -> }, keysToPreload: () -> Iterable = { emptyList() }) : - this(buildCache(concurrencyLevel, loadFunction, removalListener, keysToPreload)) + this(buildCache(loadFunction, removalListener, keysToPreload)) private companion object { - private fun buildCache(concurrencyLevel: Int, loadFunction: (K) -> V, removalListener: RemovalListener, + private fun buildCache(loadFunction: (K) -> V, removalListener: RemovalListener, keysToPreload: () -> Iterable): LoadingCache { - val builder = CacheBuilder.newBuilder().concurrencyLevel(concurrencyLevel).removalListener(removalListener) + val builder = Caffeine.newBuilder().removalListener(removalListener).executor(SameThreadExecutor.getExecutor()) return builder.build(NonInvalidatingCacheLoader(loadFunction)).apply { getAll(keysToPreload()) } @@ -23,8 +25,8 @@ class NonInvalidatingUnboundCache private constructor( } // TODO look into overriding loadAll() if we ever use it - private class NonInvalidatingCacheLoader(val loadFunction: (K) -> V) : CacheLoader() { - override fun reload(key: K, oldValue: V): ListenableFuture { + private class NonInvalidatingCacheLoader(val loadFunction: (K) -> V) : CacheLoader { + override fun reload(key: K, oldValue: V): V { throw IllegalStateException("Non invalidating cache refreshed") } diff --git a/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt index c023d113fb..d205e2be15 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt @@ -1,8 +1,7 @@ package net.corda.node.utilities -import com.google.common.cache.RemovalCause -import com.google.common.cache.RemovalListener -import com.google.common.cache.RemovalNotification +import com.github.benmanes.caffeine.cache.RemovalCause +import com.github.benmanes.caffeine.cache.RemovalListener import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.currentDBSession import java.util.* @@ -10,7 +9,7 @@ import java.util.* /** * Implements an unbound caching layer on top of a table accessed via Hibernate mapping. */ -class PersistentMap( +class PersistentMap( val toPersistentEntityKey: (K) -> EK, val fromPersistentEntity: (E) -> Pair, val toPersistentEntity: (key: K, value: V) -> E, @@ -22,7 +21,6 @@ class PersistentMap( } private val cache = NonInvalidatingUnboundCache( - concurrencyLevel = 8, loadFunction = { key -> Optional.ofNullable(loadValue(key)) }, removalListener = ExplicitRemoval(toPersistentEntityKey, persistentEntityClass) ).apply { @@ -34,11 +32,11 @@ class PersistentMap( } class ExplicitRemoval(private val toPersistentEntityKey: (K) -> EK, private val persistentEntityClass: Class) : RemovalListener { - override fun onRemoval(notification: RemovalNotification?) { - when (notification?.cause) { + override fun onRemoval(key: K?, value: V?, cause: RemovalCause) { + when (cause) { RemovalCause.EXPLICIT -> { val session = currentDBSession() - val elem = session.find(persistentEntityClass, toPersistentEntityKey(notification.key)) + val elem = session.find(persistentEntityClass, toPersistentEntityKey(key!!)) if (elem != null) { session.remove(elem) } @@ -53,14 +51,14 @@ class PersistentMap( } override operator fun get(key: K): V? { - return cache.get(key).orElse(null) + return cache.get(key)!!.orElse(null) } fun all(): Sequence> { return cache.asMap().asSequence().filter { it.value.isPresent }.map { Pair(it.key, it.value.get()) } } - override val size get() = cache.size().toInt() + override val size get() = cache.estimatedSize().toInt() private tailrec fun set(key: K, value: V, logWarning: Boolean = true, store: (K, V) -> V?, replace: (K, V) -> Unit): Boolean { var insertionAttempt = false @@ -72,7 +70,7 @@ class PersistentMap( // Store the value, depending on store implementation this may replace existing entry in DB. store(key, value) Optional.of(value) - } + }!! if (!insertionAttempt) { if (existingInCache.isPresent) { // Key already exists in cache, store the new value in the DB (depends on tore implementation) and refresh cache. @@ -165,7 +163,7 @@ class PersistentMap( * Removes the mapping for the specified key from this map and underlying storage if present. */ override fun remove(key: K): V? { - val result = cache.get(key).orElse(null) + val result = cache.get(key)!!.orElse(null) cache.invalidate(key) return result } @@ -253,7 +251,7 @@ class PersistentMap( override fun put(key: K, value: V): V? { val old = cache.get(key) addWithDuplicatesReplaced(key, value) - return old.orElse(null) + return old!!.orElse(null) } fun load() { diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt index 74f6998f93..5ec79549e8 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt @@ -1,8 +1,8 @@ package net.corda.explorer.identicon +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine import com.google.common.base.Splitter -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader import javafx.scene.SnapshotParameters import javafx.scene.canvas.Canvas import javafx.scene.canvas.GraphicsContext @@ -75,7 +75,7 @@ object IdenticonRenderer { private val renderingSize = 30.0 - private val cache = CacheBuilder.newBuilder().build(CacheLoader.from { key -> + private val cache = Caffeine.newBuilder().build(CacheLoader { key -> key?.let { render(key.hashCode(), renderingSize) } }) @@ -92,7 +92,7 @@ object IdenticonRenderer { } fun getIdenticon(hash: SecureHash): Image { - return cache.get(hash) + return cache.get(hash)!! } /** From 2cff495553943a16c055a00f03fad13a5a2296c0 Mon Sep 17 00:00:00 2001 From: igor nitto Date: Wed, 14 Mar 2018 16:42:23 +0000 Subject: [PATCH 4/4] Remove CordApps JARs from node classpath [CORDA-1135] (#2691) --- .../corda/core/transactions/LedgerTransaction.kt | 14 ++++++++++---- .../cash/selection/AbstractCashSelection.kt | 2 +- .../internal/persistence/CordaPersistence.kt | 5 +++-- .../persistence/HibernateConfiguration.kt | 16 +++++++++++++--- .../internal/serialization/amqp/MapSerializer.kt | 3 ++- node/src/main/java/CordaCaplet.java | 2 -- .../net/corda/node/internal/AbstractNode.kt | 7 ++++--- .../corda/node/internal/cordapp/CordappLoader.kt | 3 +-- .../net/corda/irs/api/NodeInterestRates.kt | 2 +- .../net/corda/testing/node/MockServices.kt | 2 +- 10 files changed, 36 insertions(+), 20 deletions(-) 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)