mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
Merge from Corda/OS
This commit is contained in:
commit
0a0c8538c4
@ -58,6 +58,7 @@ buildscript {
|
|||||||
ext.log4j_version = '2.9.1'
|
ext.log4j_version = '2.9.1'
|
||||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||||
ext.guava_version = constants.getProperty("guavaVersion")
|
ext.guava_version = constants.getProperty("guavaVersion")
|
||||||
|
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
||||||
ext.okhttp_version = '3.5.0'
|
ext.okhttp_version = '3.5.0'
|
||||||
ext.netty_version = '4.1.9.Final'
|
ext.netty_version = '4.1.9.Final'
|
||||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||||
|
@ -10,8 +10,7 @@
|
|||||||
|
|
||||||
package net.corda.client.jfx.model
|
package net.corda.client.jfx.model
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.google.common.cache.CacheLoader
|
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
@ -42,8 +41,8 @@ class NetworkIdentityModel {
|
|||||||
|
|
||||||
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
||||||
|
|
||||||
private val identityCache = CacheBuilder.newBuilder()
|
private val identityCache = Caffeine.newBuilder()
|
||||||
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from { publicKey ->
|
.build<PublicKey, ObservableValue<NodeInfo?>>({ publicKey ->
|
||||||
publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
|
publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
|
||||||
})
|
})
|
||||||
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.notaryIdentities() ?: emptyList()) })
|
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.notaryIdentities() ?: emptyList()) })
|
||||||
@ -52,5 +51,5 @@ class NetworkIdentityModel {
|
|||||||
.filtered { it.legalIdentities.all { it !in notaries } }
|
.filtered { it.legalIdentities.all { it !in notaries } }
|
||||||
val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party }
|
val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party }
|
||||||
|
|
||||||
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]
|
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]!!
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,9 @@ dependencies {
|
|||||||
compile project(':core')
|
compile project(':core')
|
||||||
compile project(':node-api')
|
compile project(':node-api')
|
||||||
|
|
||||||
|
// For caches rather than guava
|
||||||
|
compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
|
||||||
|
|
||||||
// Unit testing helpers.
|
// Unit testing helpers.
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
|
@ -54,12 +54,6 @@ data class RPCClientConfiguration(
|
|||||||
val reapInterval: Duration,
|
val reapInterval: Duration,
|
||||||
/** The number of threads to use for observations (for executing [Observable.onNext]) */
|
/** The number of threads to use for observations (for executing [Observable.onNext]) */
|
||||||
val observationExecutorPoolSize: Int,
|
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 */
|
/** The retry interval of artemis connections in milliseconds */
|
||||||
val connectionRetryInterval: Duration,
|
val connectionRetryInterval: Duration,
|
||||||
/** The retry interval multiplier for exponential backoff */
|
/** The retry interval multiplier for exponential backoff */
|
||||||
@ -81,7 +75,6 @@ data class RPCClientConfiguration(
|
|||||||
trackRpcCallSites = false,
|
trackRpcCallSites = false,
|
||||||
reapInterval = 1.seconds,
|
reapInterval = 1.seconds,
|
||||||
observationExecutorPoolSize = 4,
|
observationExecutorPoolSize = 4,
|
||||||
cacheConcurrencyLevel = 8,
|
|
||||||
connectionRetryInterval = 5.seconds,
|
connectionRetryInterval = 5.seconds,
|
||||||
connectionRetryIntervalMultiplier = 1.5,
|
connectionRetryIntervalMultiplier = 1.5,
|
||||||
connectionMaxRetryInterval = 3.minutes,
|
connectionMaxRetryInterval = 3.minutes,
|
||||||
|
@ -10,14 +10,15 @@
|
|||||||
|
|
||||||
package net.corda.client.rpc.internal
|
package net.corda.client.rpc.internal
|
||||||
|
|
||||||
|
import co.paralleluniverse.common.util.SameThreadExecutor
|
||||||
import com.esotericsoftware.kryo.Kryo
|
import com.esotericsoftware.kryo.Kryo
|
||||||
import com.esotericsoftware.kryo.Serializer
|
import com.esotericsoftware.kryo.Serializer
|
||||||
import com.esotericsoftware.kryo.io.Input
|
import com.esotericsoftware.kryo.io.Input
|
||||||
import com.esotericsoftware.kryo.io.Output
|
import com.esotericsoftware.kryo.io.Output
|
||||||
import com.google.common.cache.Cache
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.google.common.cache.RemovalCause
|
import com.github.benmanes.caffeine.cache.RemovalCause
|
||||||
import com.google.common.cache.RemovalListener
|
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||||
import net.corda.client.rpc.RPCException
|
import net.corda.client.rpc.RPCException
|
||||||
@ -152,10 +153,10 @@ class RPCClientProxyHandler(
|
|||||||
private val serializationContextWithObservableContext = RpcClientObservableSerializer.createContext(serializationContext, observableContext)
|
private val serializationContextWithObservableContext = RpcClientObservableSerializer.createContext(serializationContext, observableContext)
|
||||||
|
|
||||||
private fun createRpcObservableMap(): RpcObservableMap {
|
private fun createRpcObservableMap(): RpcObservableMap {
|
||||||
val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> {
|
val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> { key, value, cause ->
|
||||||
val observableId = it.key!!
|
val observableId = key!!
|
||||||
val rpcCallSite = callSiteMap?.remove(observableId)
|
val rpcCallSite = callSiteMap?.remove(observableId)
|
||||||
if (it.cause == RemovalCause.COLLECTED) {
|
if (cause == RemovalCause.COLLECTED) {
|
||||||
log.warn(listOf(
|
log.warn(listOf(
|
||||||
"A hot observable returned from an RPC was never subscribed to.",
|
"A hot observable returned from an RPC was never subscribed to.",
|
||||||
"This wastes server-side resources because it was queueing observations for retrieval.",
|
"This wastes server-side resources because it was queueing observations for retrieval.",
|
||||||
@ -166,10 +167,9 @@ class RPCClientProxyHandler(
|
|||||||
}
|
}
|
||||||
observablesToReap.locked { observables.add(observableId) }
|
observablesToReap.locked { observables.add(observableId) }
|
||||||
}
|
}
|
||||||
return CacheBuilder.newBuilder().
|
return Caffeine.newBuilder().
|
||||||
weakValues().
|
weakValues().
|
||||||
removalListener(onObservableRemove).
|
removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()).
|
||||||
concurrencyLevel(rpcConfiguration.cacheConcurrencyLevel).
|
|
||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,8 +101,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
return testProxy<TestOps>(
|
return testProxy<TestOps>(
|
||||||
TestOpsImpl(pool),
|
TestOpsImpl(pool),
|
||||||
clientConfiguration = RPCClientConfiguration.default.copy(
|
clientConfiguration = RPCClientConfiguration.default.copy(
|
||||||
reapInterval = 100.millis,
|
reapInterval = 100.millis
|
||||||
cacheConcurrencyLevel = 16
|
|
||||||
),
|
),
|
||||||
serverConfiguration = RPCServerConfiguration.default.copy(
|
serverConfiguration = RPCServerConfiguration.default.copy(
|
||||||
rpcThreadPoolSize = 4
|
rpcThreadPoolSize = 4
|
||||||
|
@ -98,7 +98,6 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
|||||||
rpcDriver {
|
rpcDriver {
|
||||||
val proxy = testProxy(
|
val proxy = testProxy(
|
||||||
RPCClientConfiguration.default.copy(
|
RPCClientConfiguration.default.copy(
|
||||||
cacheConcurrencyLevel = 16,
|
|
||||||
observationExecutorPoolSize = 2
|
observationExecutorPoolSize = 2
|
||||||
),
|
),
|
||||||
RPCServerConfiguration.default.copy(
|
RPCServerConfiguration.default.copy(
|
||||||
@ -138,8 +137,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
|||||||
val metricRegistry = startReporter(shutdownManager)
|
val metricRegistry = startReporter(shutdownManager)
|
||||||
val proxy = testProxy(
|
val proxy = testProxy(
|
||||||
RPCClientConfiguration.default.copy(
|
RPCClientConfiguration.default.copy(
|
||||||
reapInterval = 1.seconds,
|
reapInterval = 1.seconds
|
||||||
cacheConcurrencyLevel = 16
|
|
||||||
),
|
),
|
||||||
RPCServerConfiguration.default.copy(
|
RPCServerConfiguration.default.copy(
|
||||||
rpcThreadPoolSize = 8
|
rpcThreadPoolSize = 8
|
||||||
|
@ -17,3 +17,4 @@ typesafeConfigVersion=1.3.1
|
|||||||
jsr305Version=3.0.2
|
jsr305Version=3.0.2
|
||||||
artifactoryPluginVersion=4.4.18
|
artifactoryPluginVersion=4.4.18
|
||||||
snakeYamlVersion=1.19
|
snakeYamlVersion=1.19
|
||||||
|
caffeineVersion=2.6.2
|
||||||
|
@ -62,13 +62,19 @@ data class LedgerTransaction @JvmOverloads constructor(
|
|||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun createContractFor(className: ContractClassName): Try<Contract> {
|
private fun createContractFor(className: ContractClassName, classLoader: ClassLoader?): Try<Contract> {
|
||||||
return Try.on { this::class.java.classLoader.loadClass(className).asSubclass(Contract::class.java).getConstructor().newInstance() }
|
return Try.on {
|
||||||
|
(classLoader ?: this::class.java.classLoader)
|
||||||
|
.loadClass(className)
|
||||||
|
.asSubclass(Contract::class.java)
|
||||||
|
.getConstructor()
|
||||||
|
.newInstance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val contracts: Map<ContractClassName, Try<Contract>> = (inputs.map { it.state.contract } + outputs.map { it.contract })
|
private val contracts: Map<ContractClassName, Try<Contract>> = (inputs.map { it.state } + outputs)
|
||||||
.toSet().map { it to createContractFor(it) }.toMap()
|
.map { it.contract to createContractFor(it.contract, it.data::class.java.classLoader) }.toMap()
|
||||||
|
|
||||||
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
|
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
|
||||||
|
|
||||||
|
@ -107,11 +107,12 @@ Mac
|
|||||||
|
|
||||||
Java
|
Java
|
||||||
^^^^
|
^^^^
|
||||||
1. Open "System Preferences > Java"
|
1. Visit http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
|
||||||
2. In the Java Control Panel, if an update is available, click "Update Now"
|
2. Scroll down to "Java SE Development Kit 8uXXX" (where "XXX" is the latest minor version number)
|
||||||
3. In the "Software Update" window, click "Install Update". If required, enter your password and click "Install Helper" when prompted
|
3. Toggle "Accept License Agreement"
|
||||||
4. Wait for a pop-up window indicating that you have successfully installed the update, and click "Close"
|
4. Click the download link for jdk-8uXXX-macosx-x64.dmg (where "XXX" is the latest minor version number)
|
||||||
5. Open a new terminal and type ``java -version`` to test that Java is installed correctly
|
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
|
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 <flow-cookbook>` and the `samples <https://www.corda.net/samples/>`_ along the way.
|
:doc:`flow cookbook <flow-cookbook>` and the `samples <https://www.corda.net/samples/>`_ along the way.
|
||||||
|
|
||||||
If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the
|
If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the
|
||||||
`forums <https://discourse.corda.net/>`_ or via `slack <http://slack.corda.net/>`_.
|
`forums <https://discourse.corda.net/>`_ or via `slack <http://slack.corda.net/>`_.
|
||||||
|
@ -46,7 +46,7 @@ abstract class AbstractCashSelection(private val maxRetries : Int = 8, private v
|
|||||||
fun getInstance(metadata: () -> java.sql.DatabaseMetaData): AbstractCashSelection {
|
fun getInstance(metadata: () -> java.sql.DatabaseMetaData): AbstractCashSelection {
|
||||||
return instance.get() ?: {
|
return instance.get() ?: {
|
||||||
val _metadata = metadata()
|
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) }
|
val cashSelectionAlgo = cashSelectionAlgos.firstOrNull { it.isCompatible(_metadata) }
|
||||||
cashSelectionAlgo?.let {
|
cashSelectionAlgo?.let {
|
||||||
instance.set(cashSelectionAlgo)
|
instance.set(cashSelectionAlgo)
|
||||||
|
@ -63,6 +63,9 @@ dependencies {
|
|||||||
|
|
||||||
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||||
|
|
||||||
|
// For caches rather than guava
|
||||||
|
compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
|
||||||
|
|
||||||
// Unit testing helpers.
|
// Unit testing helpers.
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
package net.corda.nodeapi.internal
|
package net.corda.nodeapi.internal
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
import com.google.common.cache.CacheLoader
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
@ -21,11 +21,11 @@ import java.util.concurrent.atomic.AtomicLong
|
|||||||
*/
|
*/
|
||||||
class DeduplicationChecker(cacheExpiry: Duration) {
|
class DeduplicationChecker(cacheExpiry: Duration) {
|
||||||
// dedupe identity -> watermark cache
|
// dedupe identity -> watermark cache
|
||||||
private val watermarkCache = CacheBuilder.newBuilder()
|
private val watermarkCache = Caffeine.newBuilder()
|
||||||
.expireAfterAccess(cacheExpiry.toNanos(), TimeUnit.NANOSECONDS)
|
.expireAfterAccess(cacheExpiry.toNanos(), TimeUnit.NANOSECONDS)
|
||||||
.build(WatermarkCacheLoader)
|
.build(WatermarkCacheLoader)
|
||||||
|
|
||||||
private object WatermarkCacheLoader : CacheLoader<Any, AtomicLong>() {
|
private object WatermarkCacheLoader : CacheLoader<Any, AtomicLong> {
|
||||||
override fun load(key: Any) = AtomicLong(-1)
|
override fun load(key: Any) = AtomicLong(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +35,7 @@ class DeduplicationChecker(cacheExpiry: Duration) {
|
|||||||
* @return true if the message is unique, false if it's a duplicate.
|
* @return true if the message is unique, false if it's a duplicate.
|
||||||
*/
|
*/
|
||||||
fun checkDuplicateMessageId(identity: Any, sequenceNumber: Long): Boolean {
|
fun checkDuplicateMessageId(identity: Any, sequenceNumber: Long): Boolean {
|
||||||
return watermarkCache[identity].getAndUpdate { maxOf(sequenceNumber, it) } >= sequenceNumber
|
return watermarkCache[identity]!!.getAndUpdate { maxOf(sequenceNumber, it) } >= sequenceNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,8 @@ class CordaPersistence(
|
|||||||
databaseConfig: DatabaseConfig,
|
databaseConfig: DatabaseConfig,
|
||||||
schemas: Set<MappedSchema>,
|
schemas: Set<MappedSchema>,
|
||||||
val jdbcUrl: String,
|
val jdbcUrl: String,
|
||||||
attributeConverters: Collection<AttributeConverter<*, *>> = emptySet()
|
attributeConverters: Collection<AttributeConverter<*, *>> = emptySet(),
|
||||||
|
val cordappClassLoader: ClassLoader? = null
|
||||||
) : Closeable {
|
) : Closeable {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
@ -74,7 +75,7 @@ class CordaPersistence(
|
|||||||
val hibernateConfig: HibernateConfiguration by lazy {
|
val hibernateConfig: HibernateConfiguration by lazy {
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
HibernateConfiguration(schemas, databaseConfig, attributeConverters, jdbcUrl)
|
HibernateConfiguration(schemas, databaseConfig, attributeConverters, jdbcUrl, cordappClassLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas
|
val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas
|
||||||
|
@ -19,6 +19,8 @@ import org.hibernate.boot.Metadata
|
|||||||
import org.hibernate.boot.MetadataBuilder
|
import org.hibernate.boot.MetadataBuilder
|
||||||
import org.hibernate.boot.MetadataSources
|
import org.hibernate.boot.MetadataSources
|
||||||
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder
|
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.cfg.Configuration
|
||||||
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
|
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
|
||||||
import org.hibernate.service.UnknownUnwrapTypeException
|
import org.hibernate.service.UnknownUnwrapTypeException
|
||||||
@ -36,7 +38,8 @@ class HibernateConfiguration(
|
|||||||
schemas: Set<MappedSchema>,
|
schemas: Set<MappedSchema>,
|
||||||
private val databaseConfig: DatabaseConfig,
|
private val databaseConfig: DatabaseConfig,
|
||||||
private val attributeConverters: Collection<AttributeConverter<*, *>>,
|
private val attributeConverters: Collection<AttributeConverter<*, *>>,
|
||||||
private val jdbcUrl: String
|
private val jdbcUrl: String,
|
||||||
|
val cordappClassLoader: ClassLoader? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
@ -93,7 +96,7 @@ class HibernateConfiguration(
|
|||||||
schema.mappedTypes.forEach { config.addAnnotatedClass(it) }
|
schema.mappedTypes.forEach { config.addAnnotatedClass(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val sessionFactory = buildSessionFactory(config, metadataSources)
|
val sessionFactory = buildSessionFactory(config, metadataSources, cordappClassLoader)
|
||||||
logger.info("Created session factory for schemas: $schemas")
|
logger.info("Created session factory for schemas: $schemas")
|
||||||
|
|
||||||
// export Hibernate JMX statistics
|
// export Hibernate JMX statistics
|
||||||
@ -119,11 +122,17 @@ class HibernateConfiguration(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources): SessionFactory {
|
private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources, cordappClassLoader: ClassLoader?): SessionFactory {
|
||||||
config.standardServiceRegistryBuilder.applySettings(config.properties)
|
config.standardServiceRegistryBuilder.applySettings(config.properties)
|
||||||
|
|
||||||
|
if (cordappClassLoader != null) {
|
||||||
|
config.standardServiceRegistryBuilder.addService(
|
||||||
|
ClassLoaderService::class.java,
|
||||||
|
ClassLoaderServiceImpl(cordappClassLoader))
|
||||||
|
}
|
||||||
|
|
||||||
val metadataBuilder = metadataSources.getMetadataBuilder(config.standardServiceRegistryBuilder.build())
|
val metadataBuilder = metadataSources.getMetadataBuilder(config.standardServiceRegistryBuilder.build())
|
||||||
val metadata = buildHibernateMetadata(metadataBuilder, jdbcUrl, attributeConverters)
|
val metadata = buildHibernateMetadata(metadataBuilder, jdbcUrl, attributeConverters)
|
||||||
|
|
||||||
return metadata.sessionFactoryBuilder.run {
|
return metadata.sessionFactoryBuilder.run {
|
||||||
allowOutOfTransactionUpdateOperations(true)
|
allowOutOfTransactionUpdateOperations(true)
|
||||||
applySecondLevelCacheSupport(false)
|
applySecondLevelCacheSupport(false)
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
package net.corda.nodeapi.internal.serialization
|
package net.corda.nodeapi.internal.serialization
|
||||||
|
|
||||||
import com.google.common.cache.Cache
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.internal.copyBytes
|
import net.corda.core.internal.copyBytes
|
||||||
@ -40,7 +40,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
|||||||
override val useCase: SerializationContext.UseCase,
|
override val useCase: SerializationContext.UseCase,
|
||||||
override val encoding: SerializationEncoding?,
|
override val encoding: SerializationEncoding?,
|
||||||
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext {
|
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext {
|
||||||
private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build()
|
private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -59,7 +59,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
|||||||
}
|
}
|
||||||
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
|
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
|
||||||
AttachmentsClassLoader(attachments, parent = deserializationClassLoader)
|
AttachmentsClassLoader(attachments, parent = deserializationClassLoader)
|
||||||
})
|
}!!)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
// Caught from within the cache get, so unwrap.
|
// Caught from within the cache get, so unwrap.
|
||||||
throw e.cause!!
|
throw e.cause!!
|
||||||
|
@ -28,7 +28,8 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *>
|
|||||||
* Serialization / deserialization of certain supported [Map] types.
|
* Serialization / deserialization of certain supported [Map] types.
|
||||||
*/
|
*/
|
||||||
class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> {
|
class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||||
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(
|
override val typeDescriptor: Symbol = Symbol.valueOf(
|
||||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||||
|
|
||||||
|
@ -91,6 +91,9 @@ dependencies {
|
|||||||
|
|
||||||
compile "com.google.guava:guava:$guava_version"
|
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.
|
// JOpt: for command line flags.
|
||||||
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
|
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
|
||||||
|
|
||||||
|
@ -91,8 +91,6 @@ public class CordaCaplet extends Capsule {
|
|||||||
T cp = super.attribute(attr);
|
T cp = super.attribute(attr);
|
||||||
|
|
||||||
(new File(baseDir, "cordapps")).mkdir();
|
(new File(baseDir, "cordapps")).mkdir();
|
||||||
augmentClasspath((List<Path>) cp, new File(baseDir, "cordapps"));
|
|
||||||
augmentClasspath((List<Path>) cp, new File(baseDir, "plugins"));
|
|
||||||
// Add additional directories of JARs to the classpath (at the end). e.g. for JDBC drivers
|
// Add additional directories of JARs to the classpath (at the end). e.g. for JDBC drivers
|
||||||
try {
|
try {
|
||||||
List<String> jarDirs = nodeConfig.getStringList("jarDirs");
|
List<String> jarDirs = nodeConfig.getStringList("jarDirs");
|
||||||
|
@ -657,7 +657,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
val props = configuration.dataSourceProperties
|
val props = configuration.dataSourceProperties
|
||||||
if (props.isEmpty()) throw DatabaseConfigurationException("There must be a database configured.")
|
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.
|
// Now log the vendor string as this will also cause a connection to be tested eagerly.
|
||||||
logVendorString(database, log)
|
logVendorString(database, log)
|
||||||
runOnStop += database::close
|
runOnStop += database::close
|
||||||
@ -897,7 +897,8 @@ internal class NetworkMapCacheEmptyException : Exception()
|
|||||||
fun configureDatabase(hikariProperties: Properties,
|
fun configureDatabase(hikariProperties: Properties,
|
||||||
databaseConfig: DatabaseConfig,
|
databaseConfig: DatabaseConfig,
|
||||||
identityService: IdentityService,
|
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
|
// 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
|
// 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
|
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
||||||
@ -907,8 +908,6 @@ fun configureDatabase(hikariProperties: Properties,
|
|||||||
val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(identityService))
|
val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(identityService))
|
||||||
|
|
||||||
val jdbcUrl = hikariProperties.getProperty("dataSource.url", "")
|
val jdbcUrl = hikariProperties.getProperty("dataSource.url", "")
|
||||||
|
|
||||||
SchemaMigration(schemaService.schemaOptions.keys, dataSource, !isH2Database(jdbcUrl), databaseConfig).nodeStartup()
|
SchemaMigration(schemaService.schemaOptions.keys, dataSource, !isH2Database(jdbcUrl), databaseConfig).nodeStartup()
|
||||||
|
return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, attributeConverters, cordappClassLoader)
|
||||||
return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)
|
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,6 @@ import kotlin.streams.toList
|
|||||||
*/
|
*/
|
||||||
class CordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>) {
|
class CordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>) {
|
||||||
val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
|
val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
|
||||||
|
|
||||||
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 {
|
init {
|
||||||
|
@ -10,8 +10,9 @@
|
|||||||
|
|
||||||
package net.corda.node.internal.security
|
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 com.google.common.primitives.Ints
|
||||||
import net.corda.core.context.AuthServiceId
|
import net.corda.core.context.AuthServiceId
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
@ -112,7 +113,7 @@ class RPCSecurityManagerImpl(private val config: AuthServiceConfig) : RPCSecurit
|
|||||||
return DefaultSecurityManager(realm).also {
|
return DefaultSecurityManager(realm).also {
|
||||||
// Setup optional cache layer if configured
|
// Setup optional cache layer if configured
|
||||||
it.cacheManager = config.options?.cache?.let {
|
it.cacheManager = config.options?.cache?.let {
|
||||||
GuavaCacheManager(
|
CaffeineCacheManager(
|
||||||
timeToLiveSeconds = it.expireAfterSecs,
|
timeToLiveSeconds = it.expireAfterSecs,
|
||||||
maxSize = it.maxEntries)
|
maxSize = it.maxEntries)
|
||||||
}
|
}
|
||||||
@ -275,9 +276,9 @@ private class NodeJdbcRealm(config: SecurityConfiguration.AuthService.DataSource
|
|||||||
private typealias ShiroCache<K, V> = org.apache.shiro.cache.Cache<K, V>
|
private typealias ShiroCache<K, V> = org.apache.shiro.cache.Cache<K, V>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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 <K, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<K, V> {
|
private fun <K : Any, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<K, V> {
|
||||||
|
|
||||||
val name = name
|
val name = name
|
||||||
private val impl = this@toShiroCache
|
private val impl = this@toShiroCache
|
||||||
@ -300,7 +301,7 @@ private fun <K, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<
|
|||||||
impl.invalidateAll()
|
impl.invalidateAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun size() = Ints.checkedCast(impl.size())
|
override fun size() = Ints.checkedCast(impl.estimatedSize())
|
||||||
override fun keys() = impl.asMap().keys
|
override fun keys() = impl.asMap().keys
|
||||||
override fun values() = impl.asMap().values
|
override fun values() = impl.asMap().values
|
||||||
override fun toString() = "Guava cache adapter [$impl]"
|
override fun toString() = "Guava cache adapter [$impl]"
|
||||||
@ -308,22 +309,22 @@ private fun <K, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementation of [org.apache.shiro.cache.CacheManager] based on
|
* 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,
|
private class CaffeineCacheManager(val maxSize: Long,
|
||||||
val timeToLiveSeconds: Long) : CacheManager {
|
val timeToLiveSeconds: Long) : CacheManager {
|
||||||
|
|
||||||
private val instances = ConcurrentHashMap<String, ShiroCache<*, *>>()
|
private val instances = ConcurrentHashMap<String, ShiroCache<*, *>>()
|
||||||
|
|
||||||
override fun <K, V> getCache(name: String): ShiroCache<K, V> {
|
override fun <K : Any, V> getCache(name: String): ShiroCache<K, V> {
|
||||||
val result = instances[name] ?: buildCache<K, V>(name)
|
val result = instances[name] ?: buildCache<K, V>(name)
|
||||||
instances.putIfAbsent(name, result)
|
instances.putIfAbsent(name, result)
|
||||||
return result as ShiroCache<K, V>
|
return result as ShiroCache<K, V>
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <K, V> buildCache(name: String) : ShiroCache<K, V> {
|
private fun <K : Any, V> buildCache(name: String): ShiroCache<K, V> {
|
||||||
logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s")
|
logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s")
|
||||||
return CacheBuilder.newBuilder()
|
return Caffeine.newBuilder()
|
||||||
.expireAfterWrite(timeToLiveSeconds, TimeUnit.SECONDS)
|
.expireAfterWrite(timeToLiveSeconds, TimeUnit.SECONDS)
|
||||||
.maximumSize(maxSize)
|
.maximumSize(maxSize)
|
||||||
.build<K, V>()
|
.build<K, V>()
|
||||||
@ -331,6 +332,6 @@ private class GuavaCacheManager(val maxSize: Long,
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<GuavaCacheManager>()
|
private val logger = loggerFor<CaffeineCacheManager>()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,13 +10,14 @@
|
|||||||
|
|
||||||
package net.corda.node.services.messaging
|
package net.corda.node.services.messaging
|
||||||
|
|
||||||
|
import co.paralleluniverse.common.util.SameThreadExecutor
|
||||||
import com.esotericsoftware.kryo.Kryo
|
import com.esotericsoftware.kryo.Kryo
|
||||||
import com.esotericsoftware.kryo.Serializer
|
import com.esotericsoftware.kryo.Serializer
|
||||||
import com.esotericsoftware.kryo.io.Input
|
import com.esotericsoftware.kryo.io.Input
|
||||||
import com.esotericsoftware.kryo.io.Output
|
import com.esotericsoftware.kryo.io.Output
|
||||||
import com.google.common.cache.Cache
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.google.common.cache.RemovalListener
|
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||||
import com.google.common.collect.HashMultimap
|
import com.google.common.collect.HashMultimap
|
||||||
import com.google.common.collect.Multimaps
|
import com.google.common.collect.Multimaps
|
||||||
import com.google.common.collect.SetMultimap
|
import com.google.common.collect.SetMultimap
|
||||||
@ -155,11 +156,11 @@ class RPCServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createObservableSubscriptionMap(): ObservableSubscriptionMap {
|
private fun createObservableSubscriptionMap(): ObservableSubscriptionMap {
|
||||||
val onObservableRemove = RemovalListener<InvocationId, ObservableSubscription> {
|
val onObservableRemove = RemovalListener<InvocationId, ObservableSubscription> { key, value, cause ->
|
||||||
log.debug { "Unsubscribing from Observable with id ${it.key} because of ${it.cause}" }
|
log.debug { "Unsubscribing from Observable with id ${key} because of ${cause}" }
|
||||||
it.value.subscription.unsubscribe()
|
value!!.subscription.unsubscribe()
|
||||||
}
|
}
|
||||||
return CacheBuilder.newBuilder().removalListener(onObservableRemove).build()
|
return Caffeine.newBuilder().removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(activeMqServerControl: ActiveMQServerControl) {
|
fun start(activeMqServerControl: ActiveMQServerControl) {
|
||||||
|
@ -175,9 +175,9 @@ open class PersistentNetworkMapCache(
|
|||||||
|
|
||||||
override fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo> = database.transaction { queryByLegalName(session, name) }
|
override fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo> = database.transaction { queryByLegalName(session, name) }
|
||||||
|
|
||||||
override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo> = nodesByKeyCache[identityKey]
|
override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo> = nodesByKeyCache[identityKey]!!
|
||||||
|
|
||||||
private val nodesByKeyCache = NonInvalidatingCache<PublicKey, List<NodeInfo>>(1024, 8, { key -> database.transaction { queryByIdentityKey(session, key) } })
|
private val nodesByKeyCache = NonInvalidatingCache<PublicKey, List<NodeInfo>>(1024, { key -> database.transaction { queryByIdentityKey(session, key) } })
|
||||||
|
|
||||||
override fun getNodesByOwningKeyIndex(identityKeyIndex: String): List<NodeInfo> {
|
override fun getNodesByOwningKeyIndex(identityKeyIndex: String): List<NodeInfo> {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
@ -187,9 +187,9 @@ open class PersistentNetworkMapCache(
|
|||||||
|
|
||||||
override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) }
|
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<CordaX500Name, Optional<PartyAndCertificate>>(1024, 8, { name -> Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) }) })
|
private val identityByLegalNameCache = NonInvalidatingCache<CordaX500Name, Optional<PartyAndCertificate>>(1024, { name -> Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) }) })
|
||||||
|
|
||||||
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
||||||
synchronized(_changed) {
|
synchronized(_changed) {
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
package net.corda.node.services.persistence
|
package net.corda.node.services.persistence
|
||||||
|
|
||||||
import com.codahale.metrics.MetricRegistry
|
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.HashCode
|
||||||
import com.google.common.hash.Hashing
|
import com.google.common.hash.Hashing
|
||||||
import com.google.common.hash.HashingInputStream
|
import com.google.common.hash.HashingInputStream
|
||||||
@ -34,7 +34,6 @@ import net.corda.node.services.config.NodeConfiguration
|
|||||||
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
||||||
import net.corda.node.utilities.NonInvalidatingCache
|
import net.corda.node.utilities.NonInvalidatingCache
|
||||||
import net.corda.node.utilities.NonInvalidatingWeightBasedCache
|
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.NODE_DATABASE_PREFIX
|
||||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import net.corda.nodeapi.internal.withContractsInJar
|
import net.corda.nodeapi.internal.withContractsInJar
|
||||||
@ -219,7 +218,6 @@ class NodeAttachmentService(
|
|||||||
|
|
||||||
private val attachmentContentCache = NonInvalidatingWeightBasedCache<SecureHash, Optional<Pair<Attachment, ByteArray>>>(
|
private val attachmentContentCache = NonInvalidatingWeightBasedCache<SecureHash, Optional<Pair<Attachment, ByteArray>>>(
|
||||||
maxWeight = attachmentContentCacheSize,
|
maxWeight = attachmentContentCacheSize,
|
||||||
concurrencyLevel = defaultCordaCacheConcurrencyLevel,
|
|
||||||
weigher = object : Weigher<SecureHash, Optional<Pair<Attachment, ByteArray>>> {
|
weigher = object : Weigher<SecureHash, Optional<Pair<Attachment, ByteArray>>> {
|
||||||
override fun weigh(key: SecureHash, value: Optional<Pair<Attachment, ByteArray>>): Int {
|
override fun weigh(key: SecureHash, value: Optional<Pair<Attachment, ByteArray>>): Int {
|
||||||
return key.size + if (value.isPresent) value.get().second.size else 0
|
return key.size + if (value.isPresent) value.get().second.size else 0
|
||||||
@ -244,12 +242,11 @@ class NodeAttachmentService(
|
|||||||
|
|
||||||
private val attachmentCache = NonInvalidatingCache<SecureHash, Optional<Attachment>>(
|
private val attachmentCache = NonInvalidatingCache<SecureHash, Optional<Attachment>>(
|
||||||
attachmentCacheBound,
|
attachmentCacheBound,
|
||||||
defaultCordaCacheConcurrencyLevel,
|
|
||||||
{ key -> Optional.ofNullable(createAttachment(key)) }
|
{ key -> Optional.ofNullable(createAttachment(key)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createAttachment(key: SecureHash): Attachment? {
|
private fun createAttachment(key: SecureHash): Attachment? {
|
||||||
val content = attachmentContentCache.get(key)
|
val content = attachmentContentCache.get(key)!!
|
||||||
if (content.isPresent) {
|
if (content.isPresent) {
|
||||||
return content.get().first
|
return content.get().first
|
||||||
}
|
}
|
||||||
@ -259,7 +256,7 @@ class NodeAttachmentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun openAttachment(id: SecureHash): Attachment? {
|
override fun openAttachment(id: SecureHash): Attachment? {
|
||||||
val attachment = attachmentCache.get(id)
|
val attachment = attachmentCache.get(id)!!
|
||||||
if (attachment.isPresent) {
|
if (attachment.isPresent) {
|
||||||
return attachment.get()
|
return attachment.get()
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
package net.corda.node.utilities
|
package net.corda.node.utilities
|
||||||
|
|
||||||
import com.google.common.cache.LoadingCache
|
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||||
import com.google.common.cache.Weigher
|
import com.github.benmanes.caffeine.cache.Weigher
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -39,7 +39,7 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
|||||||
* Returns the value associated with the key, first loading that value from the storage if necessary.
|
* Returns the value associated with the key, first loading that value from the storage if necessary.
|
||||||
*/
|
*/
|
||||||
operator fun get(key: K): V? {
|
operator fun get(key: K): V? {
|
||||||
return cache.get(key).orElse(null)
|
return cache.get(key)!!.orElse(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val size get() = allPersisted().toList().size
|
val size get() = allPersisted().toList().size
|
||||||
@ -72,7 +72,7 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
|||||||
} else {
|
} else {
|
||||||
Optional.of(value)
|
Optional.of(value)
|
||||||
}
|
}
|
||||||
}
|
}!!
|
||||||
if (!insertionAttempt) {
|
if (!insertionAttempt) {
|
||||||
if (existingInCache.isPresent) {
|
if (existingInCache.isPresent) {
|
||||||
// Key already exists in cache, do nothing.
|
// Key already exists in cache, do nothing.
|
||||||
@ -81,7 +81,7 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
|||||||
// This happens when the key was queried before with no value associated. We invalidate the cached null
|
// 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
|
// value and recursively call set again. This is to avoid race conditions where another thread queries after
|
||||||
// the invalidate but before the set.
|
// the invalidate but before the set.
|
||||||
cache.invalidate(key)
|
cache.invalidate(key!!)
|
||||||
return set(key, value, logWarning, store)
|
return set(key, value, logWarning, store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +158,6 @@ class AppendOnlyPersistentMap<K, V, E, out EK>(
|
|||||||
//TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size
|
//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<K, Optional<V>>(
|
override val cache = NonInvalidatingCache<K, Optional<V>>(
|
||||||
bound = cacheBound,
|
bound = cacheBound,
|
||||||
concurrencyLevel = 8,
|
|
||||||
loadFunction = { key -> Optional.ofNullable(loadValue(key)) })
|
loadFunction = { key -> Optional.ofNullable(loadValue(key)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +175,6 @@ class WeightBasedAppendOnlyPersistentMap<K, V, E, out EK>(
|
|||||||
persistentEntityClass) {
|
persistentEntityClass) {
|
||||||
override val cache = NonInvalidatingWeightBasedCache<K, Optional<V>>(
|
override val cache = NonInvalidatingWeightBasedCache<K, Optional<V>>(
|
||||||
maxWeight = maxWeight,
|
maxWeight = maxWeight,
|
||||||
concurrencyLevel = 8,
|
|
||||||
weigher = object : Weigher<K, Optional<V>> {
|
weigher = object : Weigher<K, Optional<V>> {
|
||||||
override fun weigh(key: K, value: Optional<V>): Int {
|
override fun weigh(key: K, value: Optional<V>): Int {
|
||||||
return weighingFunc(key, value)
|
return weighingFunc(key, value)
|
||||||
|
@ -53,7 +53,7 @@ object JVMAgentRegistry {
|
|||||||
} else {
|
} else {
|
||||||
(this::class.java.classLoader as? URLClassLoader)
|
(this::class.java.classLoader as? URLClassLoader)
|
||||||
?.urLs
|
?.urLs
|
||||||
?.map { Paths.get(it.path) }
|
?.map { Paths.get(it.toURI()) }
|
||||||
?.firstOrNull { it.fileName.toString() == jarFileName }
|
?.firstOrNull { it.fileName.toString() == jarFileName }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,30 +10,29 @@
|
|||||||
|
|
||||||
package net.corda.node.utilities
|
package net.corda.node.utilities
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
import com.google.common.cache.CacheLoader
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.google.common.cache.LoadingCache
|
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||||
import com.google.common.cache.Weigher
|
import com.github.benmanes.caffeine.cache.Weigher
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
|
||||||
|
|
||||||
|
|
||||||
class NonInvalidatingCache<K, V> private constructor(
|
class NonInvalidatingCache<K, V> private constructor(
|
||||||
val cache: LoadingCache<K, V>
|
val cache: LoadingCache<K, V>
|
||||||
) : LoadingCache<K, V> by cache {
|
) : LoadingCache<K, V> by cache {
|
||||||
|
|
||||||
constructor(bound: Long, concurrencyLevel: Int, loadFunction: (K) -> V) :
|
constructor(bound: Long, loadFunction: (K) -> V) :
|
||||||
this(buildCache(bound, concurrencyLevel, loadFunction))
|
this(buildCache(bound, loadFunction))
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private fun <K, V> buildCache(bound: Long, concurrencyLevel: Int, loadFunction: (K) -> V): LoadingCache<K, V> {
|
private fun <K, V> buildCache(bound: Long, loadFunction: (K) -> V): LoadingCache<K, V> {
|
||||||
val builder = CacheBuilder.newBuilder().maximumSize(bound).concurrencyLevel(concurrencyLevel)
|
val builder = Caffeine.newBuilder().maximumSize(bound)
|
||||||
return builder.build(NonInvalidatingCacheLoader(loadFunction))
|
return builder.build(NonInvalidatingCacheLoader(loadFunction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO look into overriding loadAll() if we ever use it
|
// TODO look into overriding loadAll() if we ever use it
|
||||||
class NonInvalidatingCacheLoader<K, V>(val loadFunction: (K) -> V) : CacheLoader<K, V>() {
|
class NonInvalidatingCacheLoader<K, V>(val loadFunction: (K) -> V) : CacheLoader<K, V> {
|
||||||
override fun reload(key: K, oldValue: V): ListenableFuture<V> {
|
override fun reload(key: K, oldValue: V): V {
|
||||||
throw IllegalStateException("Non invalidating cache refreshed")
|
throw IllegalStateException("Non invalidating cache refreshed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,16 +43,14 @@ class NonInvalidatingCache<K, V> private constructor(
|
|||||||
class NonInvalidatingWeightBasedCache<K, V> private constructor(
|
class NonInvalidatingWeightBasedCache<K, V> private constructor(
|
||||||
val cache: LoadingCache<K, V>
|
val cache: LoadingCache<K, V>
|
||||||
) : LoadingCache<K, V> by cache {
|
) : LoadingCache<K, V> by cache {
|
||||||
constructor (maxWeight: Long, concurrencyLevel: Int, weigher: Weigher<K, V>, loadFunction: (K) -> V) :
|
constructor (maxWeight: Long, weigher: Weigher<K, V>, loadFunction: (K) -> V) :
|
||||||
this(buildCache(maxWeight, concurrencyLevel, weigher, loadFunction))
|
this(buildCache(maxWeight, weigher, loadFunction))
|
||||||
|
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private fun <K, V> buildCache(maxWeight: Long, concurrencyLevel: Int, weigher: Weigher<K, V>, loadFunction: (K) -> V): LoadingCache<K, V> {
|
private fun <K, V> buildCache(maxWeight: Long, weigher: Weigher<K, V>, loadFunction: (K) -> V): LoadingCache<K, V> {
|
||||||
val builder = CacheBuilder.newBuilder().maximumWeight(maxWeight).weigher(weigher).concurrencyLevel(concurrencyLevel)
|
val builder = Caffeine.newBuilder().maximumWeight(maxWeight).weigher(weigher)
|
||||||
return builder.build(NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction))
|
return builder.build(NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultCordaCacheConcurrencyLevel: Int = 8
|
|
@ -10,22 +10,24 @@
|
|||||||
|
|
||||||
package net.corda.node.utilities
|
package net.corda.node.utilities
|
||||||
|
|
||||||
import com.google.common.cache.*
|
import co.paralleluniverse.common.util.SameThreadExecutor
|
||||||
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.RemovalListener
|
||||||
|
|
||||||
class NonInvalidatingUnboundCache<K, V> private constructor(
|
class NonInvalidatingUnboundCache<K, V> private constructor(
|
||||||
val cache: LoadingCache<K, V>
|
val cache: LoadingCache<K, V>
|
||||||
) : LoadingCache<K, V> by cache {
|
) : LoadingCache<K, V> by cache {
|
||||||
|
|
||||||
constructor(concurrencyLevel: Int, loadFunction: (K) -> V, removalListener: RemovalListener<K, V> = RemovalListener {},
|
constructor(loadFunction: (K) -> V, removalListener: RemovalListener<K, V> = RemovalListener { key, value, cause -> },
|
||||||
keysToPreload: () -> Iterable<K> = { emptyList() }) :
|
keysToPreload: () -> Iterable<K> = { emptyList() }) :
|
||||||
this(buildCache(concurrencyLevel, loadFunction, removalListener, keysToPreload))
|
this(buildCache(loadFunction, removalListener, keysToPreload))
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private fun <K, V> buildCache(concurrencyLevel: Int, loadFunction: (K) -> V, removalListener: RemovalListener<K, V>,
|
private fun <K, V> buildCache(loadFunction: (K) -> V, removalListener: RemovalListener<K, V>,
|
||||||
keysToPreload: () -> Iterable<K>): LoadingCache<K, V> {
|
keysToPreload: () -> Iterable<K>): LoadingCache<K, V> {
|
||||||
val builder = CacheBuilder.newBuilder().concurrencyLevel(concurrencyLevel).removalListener(removalListener)
|
val builder = Caffeine.newBuilder().removalListener(removalListener).executor(SameThreadExecutor.getExecutor())
|
||||||
return builder.build(NonInvalidatingCacheLoader(loadFunction)).apply {
|
return builder.build(NonInvalidatingCacheLoader(loadFunction)).apply {
|
||||||
getAll(keysToPreload())
|
getAll(keysToPreload())
|
||||||
}
|
}
|
||||||
@ -33,8 +35,8 @@ class NonInvalidatingUnboundCache<K, V> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO look into overriding loadAll() if we ever use it
|
// TODO look into overriding loadAll() if we ever use it
|
||||||
private class NonInvalidatingCacheLoader<K, V>(val loadFunction: (K) -> V) : CacheLoader<K, V>() {
|
private class NonInvalidatingCacheLoader<K, V>(val loadFunction: (K) -> V) : CacheLoader<K, V> {
|
||||||
override fun reload(key: K, oldValue: V): ListenableFuture<V> {
|
override fun reload(key: K, oldValue: V): V {
|
||||||
throw IllegalStateException("Non invalidating cache refreshed")
|
throw IllegalStateException("Non invalidating cache refreshed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,8 @@
|
|||||||
|
|
||||||
package net.corda.node.utilities
|
package net.corda.node.utilities
|
||||||
|
|
||||||
import com.google.common.cache.RemovalCause
|
import com.github.benmanes.caffeine.cache.RemovalCause
|
||||||
import com.google.common.cache.RemovalListener
|
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||||
import com.google.common.cache.RemovalNotification
|
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -20,7 +19,7 @@ import java.util.*
|
|||||||
/**
|
/**
|
||||||
* Implements an unbound caching layer on top of a table accessed via Hibernate mapping.
|
* Implements an unbound caching layer on top of a table accessed via Hibernate mapping.
|
||||||
*/
|
*/
|
||||||
class PersistentMap<K, V, E, out EK>(
|
class PersistentMap<K : Any, V, E, out EK>(
|
||||||
val toPersistentEntityKey: (K) -> EK,
|
val toPersistentEntityKey: (K) -> EK,
|
||||||
val fromPersistentEntity: (E) -> Pair<K, V>,
|
val fromPersistentEntity: (E) -> Pair<K, V>,
|
||||||
val toPersistentEntity: (key: K, value: V) -> E,
|
val toPersistentEntity: (key: K, value: V) -> E,
|
||||||
@ -32,7 +31,6 @@ class PersistentMap<K, V, E, out EK>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val cache = NonInvalidatingUnboundCache(
|
private val cache = NonInvalidatingUnboundCache(
|
||||||
concurrencyLevel = 8,
|
|
||||||
loadFunction = { key -> Optional.ofNullable(loadValue(key)) },
|
loadFunction = { key -> Optional.ofNullable(loadValue(key)) },
|
||||||
removalListener = ExplicitRemoval(toPersistentEntityKey, persistentEntityClass)
|
removalListener = ExplicitRemoval(toPersistentEntityKey, persistentEntityClass)
|
||||||
).apply {
|
).apply {
|
||||||
@ -44,11 +42,11 @@ class PersistentMap<K, V, E, out EK>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ExplicitRemoval<K, V, E, EK>(private val toPersistentEntityKey: (K) -> EK, private val persistentEntityClass: Class<E>) : RemovalListener<K, V> {
|
class ExplicitRemoval<K, V, E, EK>(private val toPersistentEntityKey: (K) -> EK, private val persistentEntityClass: Class<E>) : RemovalListener<K, V> {
|
||||||
override fun onRemoval(notification: RemovalNotification<K, V>?) {
|
override fun onRemoval(key: K?, value: V?, cause: RemovalCause) {
|
||||||
when (notification?.cause) {
|
when (cause) {
|
||||||
RemovalCause.EXPLICIT -> {
|
RemovalCause.EXPLICIT -> {
|
||||||
val session = currentDBSession()
|
val session = currentDBSession()
|
||||||
val elem = session.find(persistentEntityClass, toPersistentEntityKey(notification.key))
|
val elem = session.find(persistentEntityClass, toPersistentEntityKey(key!!))
|
||||||
if (elem != null) {
|
if (elem != null) {
|
||||||
session.remove(elem)
|
session.remove(elem)
|
||||||
}
|
}
|
||||||
@ -63,14 +61,14 @@ class PersistentMap<K, V, E, out EK>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override operator fun get(key: K): V? {
|
override operator fun get(key: K): V? {
|
||||||
return cache.get(key).orElse(null)
|
return cache.get(key)!!.orElse(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun all(): Sequence<Pair<K, V>> {
|
fun all(): Sequence<Pair<K, V>> {
|
||||||
return cache.asMap().asSequence().filter { it.value.isPresent }.map { Pair(it.key, it.value.get()) }
|
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 {
|
private tailrec fun set(key: K, value: V, logWarning: Boolean = true, store: (K, V) -> V?, replace: (K, V) -> Unit): Boolean {
|
||||||
var insertionAttempt = false
|
var insertionAttempt = false
|
||||||
@ -82,7 +80,7 @@ class PersistentMap<K, V, E, out EK>(
|
|||||||
// Store the value, depending on store implementation this may replace existing entry in DB.
|
// Store the value, depending on store implementation this may replace existing entry in DB.
|
||||||
store(key, value)
|
store(key, value)
|
||||||
Optional.of(value)
|
Optional.of(value)
|
||||||
}
|
}!!
|
||||||
if (!insertionAttempt) {
|
if (!insertionAttempt) {
|
||||||
if (existingInCache.isPresent) {
|
if (existingInCache.isPresent) {
|
||||||
// Key already exists in cache, store the new value in the DB (depends on tore implementation) and refresh cache.
|
// Key already exists in cache, store the new value in the DB (depends on tore implementation) and refresh cache.
|
||||||
@ -175,7 +173,7 @@ class PersistentMap<K, V, E, out EK>(
|
|||||||
* Removes the mapping for the specified key from this map and underlying storage if present.
|
* Removes the mapping for the specified key from this map and underlying storage if present.
|
||||||
*/
|
*/
|
||||||
override fun remove(key: K): V? {
|
override fun remove(key: K): V? {
|
||||||
val result = cache.get(key).orElse(null)
|
val result = cache.get(key)!!.orElse(null)
|
||||||
cache.invalidate(key)
|
cache.invalidate(key)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -263,7 +261,7 @@ class PersistentMap<K, V, E, out EK>(
|
|||||||
override fun put(key: K, value: V): V? {
|
override fun put(key: K, value: V): V? {
|
||||||
val old = cache.get(key)
|
val old = cache.get(key)
|
||||||
addWithDuplicatesReplaced(key, value)
|
addWithDuplicatesReplaced(key, value)
|
||||||
return old.orElse(null)
|
return old!!.orElse(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
|
@ -170,7 +170,7 @@ object NodeInterestRates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addDefaultFixes() {
|
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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ open class MockServices private constructor(
|
|||||||
val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages)
|
val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages)
|
||||||
val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation)
|
val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation)
|
||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService, schemaService)
|
val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService, schemaService, cordappLoader.appClassLoader)
|
||||||
val mockService = database.transaction {
|
val mockService = database.transaction {
|
||||||
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
|
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
|
||||||
override val vaultService: VaultService = makeVaultService(database.hibernateConfig, schemaService)
|
override val vaultService: VaultService = makeVaultService(database.hibernateConfig, schemaService)
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
package net.corda.explorer.identicon
|
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.base.Splitter
|
||||||
import com.google.common.cache.CacheBuilder
|
|
||||||
import com.google.common.cache.CacheLoader
|
|
||||||
import javafx.scene.SnapshotParameters
|
import javafx.scene.SnapshotParameters
|
||||||
import javafx.scene.canvas.Canvas
|
import javafx.scene.canvas.Canvas
|
||||||
import javafx.scene.canvas.GraphicsContext
|
import javafx.scene.canvas.GraphicsContext
|
||||||
@ -85,7 +85,7 @@ object IdenticonRenderer {
|
|||||||
|
|
||||||
private val renderingSize = 30.0
|
private val renderingSize = 30.0
|
||||||
|
|
||||||
private val cache = CacheBuilder.newBuilder().build(CacheLoader.from<SecureHash, Image> { key ->
|
private val cache = Caffeine.newBuilder().build(CacheLoader<SecureHash, Image> { key ->
|
||||||
key?.let { render(key.hashCode(), renderingSize) }
|
key?.let { render(key.hashCode(), renderingSize) }
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ object IdenticonRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getIdenticon(hash: SecureHash): Image {
|
fun getIdenticon(hash: SecureHash): Image {
|
||||||
return cache.get(hash)
|
return cache.get(hash)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user