CORDA-599 RPCSecurityManager is no longer lateinit (#2347)

This commit is contained in:
Andrzej Cichocki 2018-01-15 19:11:00 +00:00 committed by GitHub
parent 094e96d303
commit 75e74e67a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1150 additions and 41 deletions

View File

@ -242,10 +242,14 @@ private fun IntProgression.toSpliterator(): Spliterator.OfInt {
}
fun IntProgression.stream(parallel: Boolean = false): IntStream = StreamSupport.intStream(toSpliterator(), parallel)
inline fun <reified T> Stream<out T>.toTypedArray() = toTypedArray(T::class.java)
// When toArray has filled in the array, the component type is no longer T? but T (that may itself be nullable):
inline fun <reified T> Stream<out T>.toTypedArray(): Array<T> = uncheckedCast(toArray { size -> arrayOfNulls<T>(size) })
fun <T> Stream<out T>.toTypedArray(componentType: Class<T>): Array<T> = toArray { size ->
uncheckedCast<Any, Array<T?>>(java.lang.reflect.Array.newInstance(componentType, size))
}
fun <T> Stream<out T?>.filterNotNull(): Stream<T> = uncheckedCast(filter(Objects::nonNull))
fun <K, V> Stream<out Pair<K, V>>.toMap(): Map<K, V> = collect<LinkedHashMap<K, V>>(::LinkedHashMap, { m, (k, v) -> m.put(k, v) }, { m, t -> m.putAll(t) })
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */

View File

@ -3,6 +3,7 @@ package net.corda.core.internal
import org.assertj.core.api.Assertions
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.io.Serializable
import java.util.stream.IntStream
import java.util.stream.Stream
import kotlin.test.assertEquals
@ -87,5 +88,17 @@ class InternalUtilsTest {
val b: Array<String?> = Stream.of("one", "two", null).toTypedArray()
assertEquals(Array<String?>::class.java, b.javaClass)
assertArrayEquals(arrayOf("one", "two", null), b)
val c: Array<CharSequence> = Stream.of("x", "y").toTypedArray(CharSequence::class.java)
assertEquals(Array<CharSequence>::class.java, c.javaClass)
assertArrayEquals(arrayOf("x", "y"), c)
val d: Array<CharSequence?> = Stream.of("x", "y", null).toTypedArray(uncheckedCast(CharSequence::class.java))
assertEquals(Array<CharSequence?>::class.java, d.javaClass)
assertArrayEquals(arrayOf("x", "y", null), d)
}
@Test
fun `Stream of Pairs toMap works`() {
val m: Map<Comparable<*>, Serializable> = Stream.of<Pair<Comparable<*>, Serializable>>("x" to "y", 0 to 1, "x" to '2').toMap()
assertEquals<Map<*, *>>(mapOf("x" to '2', 0 to 1), m)
}
}

View File

@ -0,0 +1,109 @@
package net.corda.lazyhub
import net.corda.core.serialization.CordaSerializable
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
/** Supertype of all exceptions thrown directly by [LazyHub]. */
@CordaSerializable
abstract class LazyHubException(message: String) : RuntimeException(message)
/** The type can't be instantiated because it is abstract, i.e. it's an interface or abstract class. */
class AbstractTypeException(message: String) : LazyHubException(message)
/**
* The class can't be instantiated because it has no public constructor.
* This is so that you can easily hide a constructor from LazyHub by making it non-public.
*/
class NoPublicConstructorsException(message: String) : LazyHubException(message)
/**
* Nullable factory return types are not supported, as LazyHub has no concept of a provider that MAY supply an object.
* If you want an optional result, use logic to decide whether to add the factory to the lazyHub.
*/
class NullableReturnTypeException(message: String) : LazyHubException(message)
/** The parameter can't be satisfied and doesn't have a default and isn't nullable. */
abstract class UnsatisfiableParamException(message: String) : LazyHubException(message)
/** No provider has been registered for the wanted type. */
class NoSuchProviderException(message: String) : UnsatisfiableParamException(message)
/**
* No provider has been registered for the component type of the wanted array.
* Note that LazyHub does not create empty arrays, make the array param type nullable to accept no elements.
* This allows you to express zero-or-more (nullable) or one-or-more via the parameter type.
*/
class UnsatisfiableArrayException(message: String) : UnsatisfiableParamException(message)
/** More than one provider has been registered for the type but at most one object is wanted. */
class TooManyProvidersException(message: String) : UnsatisfiableParamException(message)
/**
* More than one public constructor is satisfiable and there is no clear winner.
* The winner is the constructor with the most params for which LazyHub actually supplies an arg.
*/
class NoUniqueGreediestSatisfiableConstructorException(message: String) : LazyHubException(message)
/** The object being created depends on itself, i.e. it's already being instantiated/factoried. */
class CircularDependencyException(message: String) : LazyHubException(message)
/** Depend on this as a param (and add the [MutableLazyHub], which is a [LazyHubFactory], to itself) if you want to make child containers. */
interface LazyHubFactory {
fun child(): MutableLazyHub
}
/**
* Read-only interface to the lazyHub.
* Where possible, always obtain your object via a constructor/method param instead of directly from the [LazyHub].
* This results in the greatest automatic benefits to the codebase e.g. separation of concerns and ease of testing.
* A notable exception to this rule is `getAll(Unit::class)` to (idempotently) run all side-effects.
*/
interface LazyHub : LazyHubFactory {
operator fun <T : Any> get(clazz: KClass<T>) = get(clazz.java)
operator fun <T> get(clazz: Class<T>) = getOrNull(clazz) ?: throw NoSuchProviderException(clazz.toString())
fun <T : Any> getAll(clazz: KClass<T>) = getAll(clazz.java)
fun <T> getAll(clazz: Class<T>): List<T>
fun <T : Any> getOrNull(clazz: KClass<T>) = getOrNull(clazz.java)
fun <T> getOrNull(clazz: Class<T>): T?
}
/** Fully-featured interface to the lazyHub. */
interface MutableLazyHub : LazyHub {
/** Register the given object against its class and all supertypes. */
fun obj(obj: Any)
/** Like plain old [MutableLazyHub.obj] but removes all [service] providers first. */
fun <T : Any> obj(service: KClass<T>, obj: T)
/**
* Register the given class as a provider for itself and all supertypes.
* The class is instantiated at most once, using the greediest public constructor satisfiable at the time.
*/
fun impl(impl: KClass<*>)
/**
* Same as [MutableLazyHub.impl] if you don't have a static reference to the class.
* Note that Kotlin features such as nullable params and default args will not be available.
*/
fun impl(impl: Class<*>)
/** Like plain old [MutableLazyHub.impl] but removes all [service] providers first. */
fun <S : Any, T : S> impl(service: KClass<S>, impl: KClass<T>)
/** Like the [KClass] variant if you don't have a static reference fo the class. */
fun <S : Any, T : S> impl(service: KClass<S>, impl: Class<T>)
/**
* Register the given function as a provider for its **declared** return type and all supertypes.
* The function is invoked at most once. Unlike constructors, the function may have any visibility.
* By convention the function should have side-effects iff its return type is [Unit].
*/
fun factory(factory: KFunction<*>)
/** Register a factory that provides the given type from the given hub. */
fun factory(lh: LazyHub, type: KClass<*>)
/** Like plain old [MutableLazyHub.factory] but removes all [service] providers first. */
fun <S : Any, T : S> factory(service: KClass<S>, factory: KFunction<T>)
}

View File

@ -0,0 +1,200 @@
package net.corda.lazyhub
import net.corda.core.internal.filterNotNull
import net.corda.core.internal.toTypedArray
import net.corda.core.internal.uncheckedCast
import net.corda.lazyhub.JConcrete.Companion.validate
import net.corda.lazyhub.KConcrete.Companion.validate
import net.corda.lazyhub.KConstructor.Companion.validate
import java.util.*
import java.util.concurrent.Callable
import java.util.stream.Stream
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
/**
* Create a new [MutableLazyHub] with no parent.
*
* Basic usage:
* * Add classes/factories/objects to the LazyHub using [MutableLazyHub.impl], [MutableLazyHub.factory] and [MutableLazyHub.obj]
* * Then ask it for a type using [LazyHub.get] and it will create (and cache) the object graph for you
* * You can use [LazyHub.getAll] to get all objects of a type, e.g. by convention pass in [Unit] to run side-effects
*
* How it works:
* * [LazyHub.get] finds the unique registered class/factory/object for the given type (or fails)
* * If it's an object, that object is returned
* * If it's a factory, it is executed with args obtained recursively from the same LazyHub
* * If it's a class, it is instantiated using a public constructor in the same way as a factory
* * Of the public constructors that can be satisfied, the one that consumes the most args is chosen
*
* Advanced usage:
* * Use an array parameter to get one-or-more args of the component type, make it nullable for zero-or-more
* * If a LazyHub can't satisfy a type (or array param) and has a parent, it asks the parent
* * Typically the root LazyHub in the hierarchy will manage all singletons of the process
*/
fun lazyHub(): MutableLazyHub = LazyHubImpl(null)
private class SimpleProvider<T : Any>(override val obj: T) : Provider<T> {
override val type get() = obj.javaClass
}
private class LazyProvider<T>(private val busyProviders: BusyProviders, private val underlying: Any?, override val type: Class<T>, val chooseInvocation: () -> Callable<T>) : Provider<T> {
override val obj by lazy { busyProviders.runFactory(this) }
override fun toString() = underlying.toString()
}
private class Invocation<P, T>(val constructor: PublicConstructor<P, T>, val argSuppliers: List<Pair<P, ArgSupplier>>) : Callable<T> {
fun providerCount() = argSuppliers.stream().filter { (_, supplier) -> supplier.provider != null }.count() // Allow repeated providers.
override fun call() = constructor(argSuppliers)
override fun toString() = constructor.toString()
}
private class BusyProviders {
private val busyProviders = mutableMapOf<LazyProvider<*>, Callable<*>>()
fun <T> runFactory(provider: LazyProvider<T>): T {
if (busyProviders.contains(provider)) throw CircularDependencyException("Provider '$provider' is already busy: ${busyProviders.values}")
val invocation = provider.chooseInvocation()
busyProviders.put(provider, invocation)
try {
return invocation.call()
} finally {
busyProviders.remove(provider)
}
}
}
private val autotypes: Map<Class<*>, Class<*>> = mutableMapOf<Class<*>, Class<*>>().apply {
Arrays::class.java.declaredMethods.filter { it.name == "hashCode" }.map { it.parameterTypes[0].componentType }.filter { it.isPrimitive }.forEach {
val boxed = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(it, 1), 0).javaClass
put(it, boxed)
put(boxed, it)
}
}
private infix fun Class<*>.isSatisfiedBy(clazz: Class<*>): Boolean {
return isAssignableFrom(clazz) || autotypes[this] == clazz
}
private class LazyHubImpl(private val parent: LazyHubImpl?, private val busyProviders: BusyProviders = parent?.busyProviders ?: BusyProviders()) : MutableLazyHub {
private val providers = mutableMapOf<Class<*>, MutableList<Provider<*>>>()
private fun add(provider: Provider<*>, type: Class<*> = provider.type, registered: MutableSet<Class<*>> = mutableSetOf()) {
if (!registered.add(type)) return
providers[type]?.add(provider) ?: providers.put(type, mutableListOf(provider))
Stream.concat(Arrays.stream(type.interfaces), Stream.of(type.superclass, autotypes[type]).filterNotNull()).forEach {
add(provider, it, registered)
}
}
/** The non-empty list of providers, or null. */
private fun <T> findProviders(clazz: Class<T>): List<Provider<T>>? = uncheckedCast(providers[clazz]) ?: parent?.findProviders(clazz)
private fun dropAll(serviceClass: Class<*>) {
val removed = mutableSetOf<Provider<*>>()
providers.iterator().run {
while (hasNext()) {
val entry = next()
if (serviceClass isSatisfiedBy entry.key) {
removed.addAll(entry.value)
remove()
}
}
}
providers.values.iterator().run {
while (hasNext()) {
val providers = next()
providers.removeAll(removed)
if (providers.isEmpty()) remove()
}
}
}
override fun <T> getOrNull(clazz: Class<T>) = findProviders(clazz)?.run { (singleOrNull() ?: throw TooManyProvidersException(clazz.toString())).obj }
override fun <T> getAll(clazz: Class<T>) = findProviders(clazz)?.map { it.obj } ?: emptyList()
override fun child(): MutableLazyHub = LazyHubImpl(this)
override fun obj(obj: Any) = add(SimpleProvider(obj))
override fun <T : Any> obj(service: KClass<T>, obj: T) {
dropAll(service.java)
obj(obj)
}
override fun <S : Any, T : S> factory(service: KClass<S>, factory: KFunction<T>) = factory.validate().let {
dropAll(service.java)
addFactory(it)
}
override fun <S : Any, T : S> impl(service: KClass<S>, impl: KClass<T>) = impl.validate().let {
dropAll(service.java)
addConcrete(it)
}
override fun <S : Any, T : S> impl(service: KClass<S>, impl: Class<T>) = impl.validate().let {
dropAll(service.java)
addConcrete(it)
}
override fun factory(factory: KFunction<*>) = addFactory(factory.validate())
private fun <T> addFactory(factory: KConstructor<T>) {
val type = factory.kFunction.returnType.toJavaType().let { if (it == Void.TYPE) Unit::class.java else it as Class<*> }
add(LazyProvider(busyProviders, factory, uncheckedCast(type)) { factory.toInvocation() })
}
override fun factory(lh: LazyHub, type: KClass<*>) = addFactory(lh, type)
private fun <T : Any> addFactory(lh: LazyHub, type: KClass<T>) {
add(LazyProvider(busyProviders, lh, type.java) { Callable { lh[type] } })
}
override fun impl(impl: KClass<*>) = implGeneric(impl)
private fun <T : Any> implGeneric(type: KClass<T>) = addConcrete(type.validate())
override fun impl(impl: Class<*>) = implGeneric(impl)
private fun <T> implGeneric(type: Class<T>) = addConcrete(type.validate())
private fun <P : Param, T, C : PublicConstructor<P, T>> addConcrete(concrete: Concrete<T, C>) {
add(LazyProvider(busyProviders, concrete, concrete.clazz) {
var fail: UnsatisfiableParamException? = null
val satisfiable = concrete.publicConstructors.mapNotNull { constructor ->
try {
constructor.toInvocation()
} catch (e: UnsatisfiableParamException) {
fail?.addSuppressed(e) ?: run { fail = e }
null
}
}
if (satisfiable.isEmpty()) throw fail!!
val greediest = mutableListOf(satisfiable[0])
var providerCount = greediest[0].providerCount()
satisfiable.stream().skip(1).forEach next@ {
val pc = it.providerCount()
if (pc < providerCount) return@next
if (pc > providerCount) {
greediest.clear()
providerCount = pc
}
greediest += it
}
greediest.singleOrNull() ?: throw NoUniqueGreediestSatisfiableConstructorException(greediest.toString())
})
}
private fun <T> arrayProvider(arrayType: Class<*>, componentType: Class<T>): LazyProvider<Array<T>>? {
val providers = findProviders(componentType) ?: return null
return LazyProvider(busyProviders, null, uncheckedCast(arrayType)) {
Callable { providers.stream().map { it.obj }.toTypedArray(componentType) }
}
}
private fun <P : Param, T> PublicConstructor<P, T>.toInvocation() = Invocation(this, params.mapNotNull { param ->
if (param.type.isArray) {
val provider = arrayProvider(param.type, param.type.componentType)
when (provider) {
null -> param.supplierWhenUnsatisfiable()?.let { param to it }
else -> param to ArgSupplier(provider)
}
} else {
val providers = findProviders(param.type)
when (providers?.size) {
null -> param.supplierWhenUnsatisfiable()?.let { param to it }
1 -> param to ArgSupplier(providers[0])
else -> throw TooManyProvidersException(param.toString())
}
}
})
}

View File

@ -0,0 +1,130 @@
package net.corda.lazyhub
import net.corda.core.internal.toMap
import net.corda.core.internal.toTypedArray
import net.corda.core.internal.uncheckedCast
import java.lang.reflect.*
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KVisibility
import kotlin.reflect.jvm.internal.ReflectProperties
import kotlin.reflect.jvm.isAccessible
private val javaTypeDelegateField = Class.forName("kotlin.reflect.jvm.internal.KTypeImpl").getDeclaredField("javaType\$delegate").apply { isAccessible = true }
internal fun kotlin.reflect.KType.toJavaType() = (javaTypeDelegateField.get(this) as ReflectProperties.Val<*>)()
internal interface Provider<T> {
/** Most specific known type i.e. directly registered implementation class, or declared return type of factory method. */
val type: Class<T>
/** May be lazily computed. */
val obj: T
}
/** Like [Provider] but capable of supplying null. */
internal class ArgSupplier(val provider: Provider<*>?) {
companion object {
val nullSupplier = ArgSupplier(null)
}
operator fun invoke() = provider?.obj
}
/** Common interface to Kotlin/Java params. */
internal interface Param {
val type: Class<*>
/** The supplier, or null to supply nothing so the Kotlin default is used. */
fun supplierWhenUnsatisfiable(): ArgSupplier? = throw (if (type.isArray) ::UnsatisfiableArrayException else ::NoSuchProviderException)(toString())
}
internal class KParam(val kParam: KParameter) : Param {
override val type = run {
var jType = kParam.type.toJavaType()
loop@ while (true) {
jType = when (jType) {
is ParameterizedType -> jType.rawType
is TypeVariable<*> -> jType.bounds.first() // Potentially surprising but most consistent behaviour, see unit tests.
else -> break@loop
}
}
jType as Class<*>
}
override fun supplierWhenUnsatisfiable() = when {
kParam.isOptional -> null // Use default value, even if param is also nullable.
kParam.type.isMarkedNullable -> ArgSupplier.nullSupplier
else -> super.supplierWhenUnsatisfiable()
}
override fun toString() = kParam.toString()
}
internal class JParam(private val param: Parameter, private val index: Int, override val type: Class<*>) : Param {
override fun toString() = "parameter #$index ${param.name} of ${param.declaringExecutable}"
}
internal interface PublicConstructor<P, out T> {
val params: List<P>
operator fun invoke(argSuppliers: List<Pair<P, ArgSupplier>>): T
}
internal class KConstructor<out T>(val kFunction: KFunction<T>) : PublicConstructor<KParam, T> {
companion object {
fun <T> KFunction<T>.validate() = run {
if (returnType.isMarkedNullable) throw NullableReturnTypeException(toString())
isAccessible = true
KConstructor(this)
}
}
override val params = kFunction.parameters.map(::KParam)
override fun invoke(argSuppliers: List<Pair<KParam, ArgSupplier>>): T {
return kFunction.callBy(argSuppliers.stream().map { (param, supplier) -> param.kParam to supplier() }.toMap())
}
override fun toString() = kFunction.toString()
}
internal class JConstructor<out T>(private val constructor: Constructor<T>) : PublicConstructor<JParam, T> {
// Much cheaper to get the types up-front than via the Parameter API:
override val params = constructor.parameters.zip(constructor.parameterTypes).mapIndexed { i, (p, t) -> JParam(p, i, t) }
override fun invoke(argSuppliers: List<Pair<JParam, ArgSupplier>>): T {
return constructor.newInstance(*argSuppliers.stream().map { (_, supplier) -> supplier() }.toTypedArray())
}
override fun toString() = constructor.toString()
}
internal interface Concrete<T, out C : PublicConstructor<*, T>> {
val clazz: Class<T>
val publicConstructors: List<C>
}
internal class KConcrete<T : Any> private constructor(private val kClass: KClass<T>) : Concrete<T, KConstructor<T>> {
companion object {
fun <T : Any> KClass<T>.validate() = run {
if (isAbstract) throw AbstractTypeException(toString())
KConcrete(this).apply {
if (publicConstructors.isEmpty()) throw NoPublicConstructorsException(toString())
}
}
}
override val clazz get() = kClass.java
override val publicConstructors = kClass.constructors.filter { it.visibility == KVisibility.PUBLIC }.map(::KConstructor)
override fun toString() = kClass.toString()
}
internal class JConcrete<T> private constructor(override val clazz: Class<T>) : Concrete<T, JConstructor<T>> {
companion object {
fun <T> Class<T>.validate() = run {
if (Modifier.isAbstract(modifiers)) throw AbstractTypeException(toString())
JConcrete(this).apply {
if (publicConstructors.isEmpty()) throw NoPublicConstructorsException(toString())
}
}
}
override val publicConstructors = uncheckedCast<Array<out Constructor<*>>, Array<Constructor<T>>>(clazz.constructors).map(::JConstructor)
override fun toString() = clazz.toString()
}

View File

@ -28,12 +28,14 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.lazyhub.LazyHub
import net.corda.lazyhub.MutableLazyHub
import net.corda.lazyhub.lazyHub
import net.corda.node.VersionInfo
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
@ -56,7 +58,6 @@ import net.corda.node.services.transactions.*
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo
@ -144,9 +145,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
protected val runOnStop = ArrayList<() -> Any?>()
private val _nodeReadyFuture = openFuture<Unit>()
protected var networkMapClient: NetworkMapClient? = null
lateinit var securityManager: RPCSecurityManager get
/** Completes once the node has successfully registered with the network map service
* or has loaded network map data from local database */
val nodeReadyFuture: CordaFuture<Unit> get() = _nodeReadyFuture
@ -200,22 +198,31 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
}
protected open fun configure(lh: MutableLazyHub) {
// TODO: Migrate classes and factories from start method.
}
open fun start(): StartedNode<AbstractNode> {
check(started == null) { "Node has already been started" }
log.info("Node starting up ...")
initCertificate()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val lh = lazyHub()
configure(lh)
val identityService = makeIdentityService(identity.certificate)
lh.obj(identityService)
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
retrieveNetworkParameters(identityService.trustRoot)
// Do all of this in a database transaction so anything that might need a connection has one.
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
lh.obj(database)
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair)
lh.obj(info)
identityService.loadIdentities(info.legalIdentitiesAndCerts)
val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes)
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache)
val nodeServices = makeServices(lh, keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache)
val notaryService = makeNotaryService(nodeServices, database)
val smm = makeStateMachineManager(database)
val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
@ -238,13 +245,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService, flowLogicRefFactory)
val rpcOps = makeRPCOps(flowStarter, database, smm)
startMessagingService(rpcOps)
lh.obj(rpcOps)
lh.getAll(Unit::class) // Run side-effects.
installCoreFlows()
val cordaServices = installCordaServices(flowStarter)
tokenizableServices = nodeServices + cordaServices + schedulerService
registerCordappFlows(smm)
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
startShell(rpcOps)
Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService)
}
val networkMapUpdater = NetworkMapUpdater(services.networkMapCache,
@ -280,10 +287,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
*/
protected abstract fun getRxIoScheduler(): Scheduler
open fun startShell(rpcOps: CordaRPCOps) {
InteractiveShell.startShell(configuration, rpcOps, securityManager, _services.identityService, _services.database)
}
private fun initNodeInfo(networkMapCache: NetworkMapCacheBaseInternal,
identity: PartyAndCertificate,
identityKeyPair: KeyPair): Pair<Set<KeyPair>, NodeInfo> {
@ -534,7 +537,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context.
*/
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList<Any> {
private fun makeServices(lh: LazyHub, keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics)
@ -550,7 +553,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
database,
info,
networkMapCache)
network = makeMessagingService(database, info)
network = lh[MessagingService::class] // TODO: Retire the lateinit var.
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
services.keyManagementService, services.identityService, platformClock,
services.auditService, services.monitoringService, services.networkMapCache, services.schemaService,
@ -715,9 +718,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
_started = null
}
protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService
protected abstract fun startMessagingService(rpcOps: RPCOps)
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)

View File

@ -5,6 +5,7 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
@ -14,8 +15,10 @@ import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.lazyhub.MutableLazyHub
import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.api.SchemaService
@ -24,6 +27,7 @@ import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.VerifierType
import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.DemoClock
@ -133,16 +137,27 @@ open class Node(configuration: NodeConfiguration,
private var messageBroker: ArtemisMessagingServer? = null
private var shutdownHook: ShutdownHook? = null
override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService {
override fun configure(lh: MutableLazyHub) {
super.configure(lh)
// Construct security manager reading users data either from the 'security' config section
// if present or from rpcUsers list if the former is missing from config.
val securityManagerConfig = configuration.security?.authService ?:
SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers)
lh.obj(configuration.security?.authService ?: SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers))
lh.impl(RPCSecurityManagerImpl::class)
configuration.messagingServerAddress?.also {
lh.obj(MessagingServerAddress(it))
} ?: run {
lh.factory(this::makeLocalMessageBroker)
}
lh.factory(this::makeMessagingService)
// Side-effects:
lh.factory(this::startMessagingService)
lh.factory(this::startShell)
}
securityManager = RPCSecurityManagerImpl(securityManagerConfig)
class MessagingServerAddress(val address: NetworkHostAndPort)
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
private fun makeMessagingService(database: CordaPersistence, info: NodeInfo, messagingServerAddress: MessagingServerAddress): MessagingService {
val serverAddress = messagingServerAddress.address
val advertisedAddress = info.addresses.single()
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
@ -162,10 +177,10 @@ open class Node(configuration: NodeConfiguration,
networkParameters.maxMessageSize)
}
private fun makeLocalMessageBroker(): NetworkHostAndPort {
private fun makeLocalMessageBroker(securityManager: RPCSecurityManager): MessagingServerAddress {
with(configuration) {
messageBroker = ArtemisMessagingServer(this, p2pAddress.port, rpcAddress?.port, services.networkMapCache, securityManager, networkParameters.maxMessageSize)
return NetworkHostAndPort("localhost", p2pAddress.port)
return MessagingServerAddress(NetworkHostAndPort("localhost", p2pAddress.port))
}
}
@ -213,7 +228,7 @@ open class Node(configuration: NodeConfiguration,
}
}
override fun startMessagingService(rpcOps: RPCOps) {
private fun startMessagingService(rpcOps: RPCOps, securityManager: RPCSecurityManager) {
// Start up the embedded MQ server
messageBroker?.apply {
runOnStop += this::stop
@ -234,6 +249,10 @@ open class Node(configuration: NodeConfiguration,
}
}
private fun startShell(rpcOps: CordaRPCOps, securityManager: RPCSecurityManager, identityService: IdentityService, database: CordaPersistence) {
InteractiveShell.startShell(configuration, rpcOps, securityManager, identityService, database)
}
/**
* If the node is persisting to an embedded H2 database, then expose this via TCP with a DB URL of the form:
* jdbc:h2:tcp://<host>:<port>/node

View File

@ -0,0 +1,640 @@
package net.corda.lazyhub
import net.corda.core.internal.uncheckedCast
import org.assertj.core.api.Assertions.catchThrowable
import org.hamcrest.CoreMatchers.*
import org.junit.Assert.*
import org.junit.Ignore
import org.junit.Test
import java.io.Closeable
import java.io.IOException
import java.io.Serializable
import kotlin.reflect.KFunction
import kotlin.reflect.jvm.javaConstructor
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertEquals
import kotlin.test.fail
open class LazyHubTests {
private val lh = lazyHub()
class Config(val info: String)
interface A
interface B {
val a: A
}
class AImpl(val config: Config) : A
class BImpl(override val a: A) : B
class Spectator {
init {
fail("Should not be instantiated.")
}
}
@Test
fun `basic functionality`() {
val config = Config("woo")
lh.obj(config)
lh.impl(AImpl::class)
lh.impl(BImpl::class)
lh.impl(Spectator::class)
val b = lh[B::class]
// An impl is instantiated at most once per LazyHub:
assertSame(b.a, lh[A::class])
assertSame(b, lh[B::class])
// More specific type to expose config without casting:
val a = lh[AImpl::class]
assertSame(b.a, a)
assertSame(config, a.config)
}
private fun createA(config: Config): A = AImpl(config) // Declared return type is significant.
internal open fun createB(): B = fail("Should not be called.")
@Test
fun `factory works`() {
lh.obj(Config("x"))
lh.factory(this::createA) // Observe private is OK.
assertSame(AImpl::class.java, lh[A::class].javaClass)
// The factory declares A not AImpl as its return type, and lh doesn't try to be clever:
catchThrowable { lh[AImpl::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(AImpl::class.toString(), message)
}
}
@Ignore
class Subclass : LazyHubTests() { // Should not run as tests.
@Suppress("unused")
private fun createA(@Suppress("UNUSED_PARAMETER") config: Config): A = fail("Should not be called.")
override fun createB() = BImpl(AImpl(Config("Subclass"))) // More specific return type is OK.
}
@Suppress("MemberVisibilityCanPrivate")
internal fun addCreateATo(lh: MutableLazyHub) {
lh.factory(this::createA)
}
@Suppress("MemberVisibilityCanPrivate")
internal fun addCreateBTo(lh: MutableLazyHub) {
lh.factory(this::createB)
}
@Test
fun `private factory is not virtual`() {
val baseMethod = this::createA.javaMethod!!
// Check the Subclass version would override if baseMethod wasn't private:
Subclass::class.java.getDeclaredMethod(baseMethod.name, *baseMethod.parameterTypes)
lh.obj(Config("x"))
Subclass().addCreateATo(lh)
lh[A::class] // Should not blow up.
}
@Test
fun `non-private factory is virtual`() {
Subclass().addCreateBTo(lh)
assertEquals("Subclass", (lh[B::class].a as AImpl).config.info) // Check overridden function was called.
// The signature that was added declares B not BImpl as its return type:
catchThrowable { lh[BImpl::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(BImpl::class.toString(), message)
}
}
private fun returnsYay() = "yay"
class TakesString(@Suppress("UNUSED_PARAMETER") text: String)
@Test
fun `too many providers`() {
lh.obj("woo")
lh.factory(this::returnsYay)
lh.impl(TakesString::class)
catchThrowable { lh[TakesString::class] }.run {
assertSame(TooManyProvidersException::class.java, javaClass)
assertEquals(TakesString::class.constructors.single().parameters[0].toString(), message)
assertThat(message, containsString(" #0 "))
assertThat(message, endsWith(TakesString::class.qualifiedName))
}
}
class TakesStringOrInt(val text: String) {
@Suppress("unused")
constructor(number: Int) : this(number.toString())
}
@Test
fun `too many providers with alternate constructor`() {
lh.obj("woo")
lh.factory(this::returnsYay)
lh.impl(TakesStringOrInt::class)
val constructors = TakesStringOrInt::class.constructors.toList()
catchThrowable { lh[TakesStringOrInt::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(constructors[0].parameters[0].toString(), message)
assertThat(message, containsString(" #0 "))
assertThat(message, endsWith(TakesStringOrInt::class.qualifiedName))
suppressed.single().run {
assertSame(TooManyProvidersException::class.java, javaClass)
assertEquals(constructors[1].parameters[0].toString(), message)
assertThat(message, containsString(" #0 "))
assertThat(message, endsWith(TakesStringOrInt::class.qualifiedName))
}
}
lh.obj(123)
assertEquals("123", lh[TakesStringOrInt::class].text)
}
@Test
fun genericClass() {
class G<out T : Serializable>(val arg: T)
lh.obj("arg")
lh.impl(G::class)
assertEquals("arg", lh[G::class].arg) // Can't inspect type arg T as no such thing exists.
}
private fun <X : Closeable, Y : X> ntv(a: Y) = a.toString()
@Test
fun `nested type variable`() {
// First check it's actually legal to pass any old Closeable into the function:
val arg = Closeable {}
assertEquals(arg.toString(), ntv(arg))
// Good, now check LazyHub can do it:
val ntv: Function1<Closeable, String> = this::ntv
lh.factory(uncheckedCast<Any, KFunction<String>>(ntv))
lh.obj(arg)
assertEquals(arg.toString(), lh[String::class])
}
class PTWMB<out Y>(val arg: Y) where Y : Closeable, Y : Serializable
private class CloseableAndSerializable : Closeable, Serializable {
override fun close() {}
}
@Test
fun `parameter type with multiple bounds in java`() {
// At compile time we must pass something Closeable and Serializable into the constructor:
CloseableAndSerializable().let { assertSame(it, PTWMB(it).arg) }
// But at runtime only Closeable is needed (and Serializable is not enough) due to the leftmost bound erasure rule:
lh.impl(PTWMB::class.java)
lh.obj(object : Serializable {})
catchThrowable { lh[PTWMB::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertThat(message, containsString(" #0 "))
assertThat(message, endsWith(PTWMB::class.constructors.single().javaConstructor.toString()))
}
val arg = Closeable {}
lh.obj(arg)
assertSame(arg, lh[PTWMB::class].arg)
}
@Test
fun `parameter type with multiple bounds in kotlin`() {
lh.impl(PTWMB::class)
lh.obj(object : Serializable {})
catchThrowable { lh[PTWMB::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(PTWMB::class.constructors.single().parameters[0].toString(), message)
assertThat(message, containsString(" #0 "))
assertThat(message, containsString(PTWMB::class.qualifiedName))
}
val arg = Closeable {}
lh.obj(arg)
assertSame(arg, lh[PTWMB::class].arg)
}
private fun <Y> ptwmb(arg: Y) where Y : Closeable, Y : Serializable = arg.toString()
@Test
fun `factory parameter type with multiple bounds`() {
val ptwmb: Function1<CloseableAndSerializable, String> = this::ptwmb
val kFunction = uncheckedCast<Any, KFunction<String>>(ptwmb)
lh.factory(kFunction)
lh.obj(object : Serializable {})
catchThrowable { lh[String::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(kFunction.parameters[0].toString(), message)
assertThat(message, containsString(" #0 "))
assertThat(message, endsWith(ptwmb.toString()))
}
val arg = Closeable {}
lh.obj(arg)
assertEquals(arg.toString(), lh[String::class])
}
private fun <Y> upt(a: Y) = a.toString()
@Test
fun `unbounded parameter type`() {
val upt: Function1<Any, String> = this::upt
val kFunction: KFunction<String> = uncheckedCast(upt)
lh.factory(kFunction)
// The only provider for Any is the factory, which is busy:
catchThrowable { lh[String::class] }.run {
assertSame(CircularDependencyException::class.java, javaClass)
assertThat(message, containsString("'$upt'"))
assertThat(message, endsWith(listOf(upt).toString()))
}
lh.obj(Any())
// This time the factory isn't attempted:
catchThrowable { lh[String::class] }.run {
assertSame(TooManyProvidersException::class.java, javaClass)
assertEquals(kFunction.parameters[0].toString(), message)
assertThat(message, containsString(" #0 "))
assertThat(message, endsWith(upt.toString()))
}
}
open class NoPublicConstructor protected constructor()
@Test
fun `no public constructor`() {
catchThrowable { lh.impl(NoPublicConstructor::class) }.run {
assertSame(NoPublicConstructorsException::class.java, javaClass)
assertEquals(NoPublicConstructor::class.toString(), message)
}
catchThrowable { lh.impl(NoPublicConstructor::class.java) }.run {
assertSame(NoPublicConstructorsException::class.java, javaClass)
assertEquals(NoPublicConstructor::class.toString(), message)
}
}
private fun primitiveInt() = 1
class IntConsumer(@Suppress("UNUSED_PARAMETER") i: Int)
class IntegerConsumer(@Suppress("UNUSED_PARAMETER") i: Int?)
@Test
fun `boxed satisfies primitive`() {
lh.obj(1)
lh.impl(IntConsumer::class)
lh[IntConsumer::class]
}
@Test
fun `primitive satisfies boxed`() {
lh.factory(this::primitiveInt)
lh.impl(IntegerConsumer::class.java)
lh[IntegerConsumer::class]
}
// The primary constructor takes two distinct providers:
class TakesTwoThings(@Suppress("UNUSED_PARAMETER") first: String, @Suppress("UNUSED_PARAMETER") second: Int) {
// This constructor takes one repeated provider but we count it both times so greediness is 2:
@Suppress("unused")
constructor(first: Int, second: Int) : this(first.toString(), second)
// This constructor would be greediest but is not satisfiable:
@Suppress("unused")
constructor(first: Int, second: String, @Suppress("UNUSED_PARAMETER") third: Config) : this(second, first)
}
@Test
fun `equally greedy constructors kotlin`() {
lh.obj("str")
lh.obj(123)
lh.impl(TakesTwoThings::class)
catchThrowable { lh[TakesTwoThings::class] }.run {
assertSame(NoUniqueGreediestSatisfiableConstructorException::class.java, javaClass)
val expected = TakesTwoThings::class.constructors.filter { it.parameters.size == 2 }
assertEquals(2, expected.size)
assertThat(message, endsWith(expected.toString()))
}
}
@Test
fun `equally greedy constructors java`() {
lh.obj("str")
lh.obj(123)
lh.impl(TakesTwoThings::class.java)
catchThrowable { lh[TakesTwoThings::class] }.run {
assertSame(NoUniqueGreediestSatisfiableConstructorException::class.java, javaClass)
val expected = TakesTwoThings::class.java.constructors.filter { it.parameters.size == 2 }
assertEquals(2, expected.size)
assertEquals(expected.toString(), message)
}
}
private fun nrt(): String? = fail("Should not be invoked.")
@Test
fun `nullable return type is banned`() {
catchThrowable { lh.factory(this::nrt) }.run {
assertSame(NullableReturnTypeException::class.java, javaClass)
assertThat(message, endsWith(this@LazyHubTests::nrt.toString()))
}
}
@Test
fun unsatisfiableArrayParam() {
class Impl(@Suppress("UNUSED_PARAMETER") v: Array<String>)
lh.impl(Impl::class)
catchThrowable { lh[Impl::class] }.run {
assertSame(UnsatisfiableArrayException::class.java, javaClass)
assertEquals(Impl::class.constructors.single().parameters[0].toString(), message)
}
// Arrays are only special in real params, you should use getAll to get all the Strings:
catchThrowable { lh[Array<String>::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(Array<String>::class.java.toString(), message)
}
assertEquals(emptyList(), lh.getAll(String::class))
}
@Test
fun arrayParam1() {
class Impl(val v: Array<String>)
lh.impl(Impl::class)
lh.obj("a")
assertArrayEquals(arrayOf("a"), lh[Impl::class].v)
}
@Test
fun arrayParam2() {
class Impl(val v: Array<String>)
lh.impl(Impl::class)
lh.obj("y")
lh.obj("x")
assertArrayEquals(arrayOf("y", "x"), lh[Impl::class].v)
}
@Test
fun nullableArrayParam() {
class Impl(val v: Array<String>?)
lh.impl(Impl::class)
assertEquals(null, lh[Impl::class].v)
}
@Test
fun arraysAreNotCached() {
class B(val v: Array<String>)
class A(val v: Array<String>, val b: B)
class C(val v: Array<String>)
class D(val v: Array<String>)
lh.obj("x")
lh.obj("y")
lh.impl(A::class)
lh.impl(B::class)
val a = lh[A::class]
a.run {
assertArrayEquals(arrayOf("x", "y"), v)
assertArrayEquals(arrayOf("x", "y"), b.v)
assertNotSame(v, b.v)
}
assertSame(lh[B::class].v, a.b.v) // Because it's the same (cached) instance of B.
lh.impl(C::class)
lh[C::class].run {
assertArrayEquals(arrayOf("x", "y"), v)
assertNotSame(v, a.v)
assertNotSame(v, a.b.v)
}
lh.obj("z")
lh.impl(D::class)
lh[D::class].run {
assertArrayEquals(arrayOf("x", "y", "z"), v)
}
}
class C1(@Suppress("UNUSED_PARAMETER") c2: C2)
class C2(@Suppress("UNUSED_PARAMETER") c3: String)
private fun c3(@Suppress("UNUSED_PARAMETER") c2: C2): String {
fail("Should not be called.")
}
@Test
fun `circularity error kotlin`() {
lh.impl(C1::class)
lh.impl(C2::class)
lh.factory(this::c3)
catchThrowable { lh[C1::class] }.run {
assertSame(CircularDependencyException::class.java, javaClass)
assertThat(message, containsString("'${C2::class}'"))
assertThat(message, endsWith(listOf(C1::class.constructors.single(), C2::class.constructors.single(), this@LazyHubTests::c3).toString()))
}
}
@Test
fun `circularity error java`() {
lh.impl(C1::class.java)
lh.impl(C2::class.java)
lh.factory(this::c3)
catchThrowable { lh[C1::class] }.run {
assertSame(CircularDependencyException::class.java, javaClass)
assertThat(message, containsString("'${C2::class}'"))
assertThat(message, endsWith(listOf(C1::class.constructors.single().javaConstructor, C2::class.constructors.single().javaConstructor, this@LazyHubTests::c3).toString()))
}
}
@Test
fun `ancestor hub providers are visible`() {
val c = Config("over here")
lh.obj(c)
lh.child().also {
it.impl(AImpl::class)
assertSame(c, it[AImpl::class].config)
}
lh.child().child().also {
it.impl(AImpl::class)
assertSame(c, it[AImpl::class].config)
}
}
@Test
fun `descendant hub providers are not visible`() {
val child = lh.child()
child.obj(Config("over here"))
lh.impl(AImpl::class)
catchThrowable { lh[AImpl::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(AImpl::class.constructors.single().parameters.single().toString(), message)
}
// Fails even though we go via the child, as the cached AImpl in lh shouldn't have collaborators from descendant hubs:
catchThrowable { child[AImpl::class] }.run {
assertSame(NoSuchProviderException::class.java, javaClass)
assertEquals(AImpl::class.constructors.single().parameters.single().toString(), message)
}
}
class AllConfigs(val configs: Array<Config>)
@Test
fun `nearest ancestor with at least one provider wins`() {
lh.obj(Config("deep"))
lh.child().also {
it.child().also {
it.impl(AllConfigs::class)
assertEquals(listOf("deep"), it[AllConfigs::class].configs.map { it.info })
}
it.obj(Config("shallow1"))
it.obj(Config("shallow2"))
it.child().also {
it.impl(AllConfigs::class)
assertEquals(listOf("shallow1", "shallow2"), it[AllConfigs::class].configs.map { it.info })
}
it.child().also {
it.obj(Config("local"))
it.impl(AllConfigs::class)
assertEquals(listOf("local"), it[AllConfigs::class].configs.map { it.info })
}
}
}
@Test
fun `abstract type`() {
catchThrowable { lh.impl(Runnable::class) }.run {
assertSame(AbstractTypeException::class.java, javaClass)
assertEquals(Runnable::class.toString(), message)
}
catchThrowable { lh.impl(Runnable::class.java) }.run {
assertSame(AbstractTypeException::class.java, javaClass)
assertEquals(Runnable::class.java.toString(), message)
}
}
private interface Service
open class GoodService : Service
abstract class BadService1 : Service
class BadService2 private constructor() : Service
private fun badService3(): Service? = fail("Should not be called.")
@Test
fun `existing providers not removed if new type is bad`() {
lh.impl(GoodService::class)
catchThrowable { lh.impl(Service::class, BadService1::class) }.run {
assertSame(AbstractTypeException::class.java, javaClass)
assertEquals(BadService1::class.toString(), message)
}
catchThrowable { lh.impl(Service::class, BadService2::class) }.run {
assertSame(NoPublicConstructorsException::class.java, javaClass)
assertEquals(BadService2::class.toString(), message)
}
catchThrowable { lh.impl(Service::class, BadService2::class.java) }.run {
assertSame(NoPublicConstructorsException::class.java, javaClass)
assertEquals(BadService2::class.toString(), message)
}
// Type system won't let you pass in badService3, but I still want validation up-front:
catchThrowable { lh.factory(Service::class, uncheckedCast(this::badService3)) }.run {
assertSame(NullableReturnTypeException::class.java, javaClass)
assertEquals(this@LazyHubTests::badService3.toString(), message)
}
assertSame(GoodService::class.java, lh[Service::class].javaClass)
}
class GoodService2 : GoodService()
@Test
fun `service providers are removed completely`() {
lh.impl(GoodService::class)
assertSame(GoodService::class.java, lh[Service::class].javaClass)
lh.impl(GoodService::class, GoodService2::class)
// In particular, GoodService is no longer registered against Service (or Any):
assertSame(GoodService2::class.java, lh[Service::class].javaClass)
assertSame(GoodService2::class.java, lh[Any::class].javaClass)
}
class JParamExample(@Suppress("UNUSED_PARAMETER") str: String, @Suppress("UNUSED_PARAMETER") num: Int)
@Test
fun `JParam has useful toString`() {
val c = JParamExample::class.java.constructors.single()
// Parameter doesn't expose its index, here we deliberately pass in the wrong one to see what happens:
val text = JParam(c.parameters[0], 1, IOException::class.java).toString()
assertThat(text, containsString(" #1 "))
assertThat(text, anyOf(containsString(" str "), containsString(" arg0 ")))
assertThat(text, endsWith(c.toString()))
}
private val sideEffects = mutableListOf<Int>()
private fun sideEffect1() {
sideEffects.add(1)
}
private fun sideEffect2() {
sideEffects.add(2)
}
@Test
fun `side-effects are idempotent as a consequence of caching of results`() {
lh.factory(this::sideEffect1)
assertEquals(listOf(Unit), lh.getAll(Unit::class))
assertEquals(listOf(1), sideEffects)
lh.factory(this::sideEffect2)
assertEquals(listOf(Unit, Unit), lh.getAll(Unit::class)) // Get both results.
assertEquals(listOf(1, 2), sideEffects) // sideEffect1 didn't run again.
}
@Test
fun `getAll returns empty list when there is nothing to return`() {
// This is in contrast to the exception thrown by an array param, which would not be useful to replicate here:
assertEquals(emptyList(), lh.getAll(IOException::class))
}
// Two params needed to make primary constructor the winner when both are satisfiable.
// It's probably true that the secondary will always trigger a CircularDependencyException, but LazyHub isn't clever enough to tell.
class InvocationSwitcher(@Suppress("UNUSED_PARAMETER") s: String, @Suppress("UNUSED_PARAMETER") t: String) {
@Suppress("unused")
constructor(same: InvocationSwitcher) : this(same.toString(), same.toString())
}
@Test
fun `chosen constructor is not set in stone`() {
lh.impl(InvocationSwitcher::class)
assertSame(CircularDependencyException::class.java, catchThrowable { lh[InvocationSwitcher::class] }.javaClass)
lh.obj("alt")
lh[InvocationSwitcher::class] // Succeeds via other constructor.
}
class GreedinessUnits(@Suppress("UNUSED_PARAMETER") v: Array<String>, @Suppress("UNUSED_PARAMETER") z: Int) {
// Two greediness units even though it's one provider repeated:
@Suppress("unused")
constructor(z1: Int, z2: Int) : this(emptyArray(), z1 + z2)
}
@Test
fun `array param counts as one greediness unit`() {
lh.obj("x")
lh.obj("y")
lh.obj(100)
lh.impl(GreedinessUnits::class)
assertSame(NoUniqueGreediestSatisfiableConstructorException::class.java, catchThrowable { lh[GreedinessUnits::class] }.javaClass)
}
interface TriangleBase
interface TriangleSide : TriangleBase
class TriangleImpl : TriangleBase, TriangleSide
@Test
fun `provider registered exactly once against each supertype`() {
lh.impl(TriangleImpl::class)
lh[TriangleBase::class] // Don't throw TooManyProvidersException.
}
interface Service1
interface Service2
class ServiceImpl1 : Service1, Service2
class ServiceImpl2 : Service2
@Test
fun `do not leak empty provider list`() {
lh.impl(ServiceImpl1::class)
lh.impl(Service2::class, ServiceImpl2::class)
assertSame(NoSuchProviderException::class.java, catchThrowable { lh[Service1::class] }.javaClass)
}
class Global
class Session(val global: Global, val local: Int)
@Test
fun `child can be used to create a scope`() {
lh.impl(Global::class)
lh.factory(lh.child().also {
it.obj(1)
it.impl(Session::class)
}, Session::class)
lh.factory(lh.child().also {
it.obj(2)
it.impl(Session::class)
}, Session::class)
val sessions = lh.getAll(Session::class)
val g = lh[Global::class]
sessions.forEach { assertSame(g, it.global) }
assertEquals(listOf(1, 2), sessions.map { it.local })
}
}

View File

@ -5,7 +5,6 @@ import com.google.common.jimfs.Jimfs
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.DoNotImplement
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
@ -15,17 +14,15 @@ import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.createDirectories
import net.corda.core.internal.createDirectory
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds
import net.corda.lazyhub.MutableLazyHub
import net.corda.node.VersionInfo
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.StartedNode
@ -294,9 +291,14 @@ open class MockNetwork(private val cordappPackages: List<String>,
}
}
override fun configure(lh: MutableLazyHub) {
super.configure(lh)
lh.factory(this::makeMessagingService)
}
// We only need to override the messaging service here, as currently everything that hits disk does so
// through the java.nio API which we are already mocking via Jimfs.
override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService {
private fun makeMessagingService(database: CordaPersistence): MessagingService {
require(id >= 0) { "Node ID must be zero or positive, was passed: " + id }
return mockNet.messagingNetwork.createNodeWithID(
!mockNet.threadPerNode,
@ -315,14 +317,6 @@ open class MockNetwork(private val cordappPackages: List<String>,
return E2ETestKeyManagementService(identityService, keyPairs)
}
override fun startShell(rpcOps: CordaRPCOps) {
//No mock shell
}
override fun startMessagingService(rpcOps: RPCOps) {
// Nothing to do
}
// This is not thread safe, but node construction is done on a single thread, so that should always be fine
override fun generateKeyPair(): KeyPair {
counter = counter.add(BigInteger.ONE)