CORDA-2653 - ensure that during initialisation of a Corda Service, the current thread has a context classloader. (#4907)

This commit is contained in:
dazraf 2019-03-20 16:42:37 +00:00 committed by Gavin Thomas
parent eb7928d761
commit 52ec48d63d
2 changed files with 47 additions and 17 deletions

View File

@ -575,21 +575,33 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private fun installCordaServices() {
val loadedServices = cordappLoader.cordapps.flatMap { it.services }
loadedServices.forEach {
try {
installCordaService(it)
} catch (e: NoSuchMethodException) {
log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " +
ServiceHub::class.java.name)
} catch (e: ServiceInstantiationException) {
if (e.cause != null) {
log.error("Corda service ${it.name} failed to instantiate. Reason was: ${e.cause?.rootMessage}", e.cause)
} else {
log.error("Corda service ${it.name} failed to instantiate", e)
// This sets the Cordapp classloader on the contextClassLoader of the current thread, prior to initializing services
// Needed because of bug CORDA-2653 - some Corda services can utilise third-party libraries that require access to
// the Thread context class loader
val oldContextClassLoader : ClassLoader? = Thread.currentThread().contextClassLoader
try {
Thread.currentThread().contextClassLoader = cordappLoader.appClassLoader
loadedServices.forEach {
try {
installCordaService(it)
} catch (e: NoSuchMethodException) {
log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " +
ServiceHub::class.java.name)
} catch (e: ServiceInstantiationException) {
if (e.cause != null) {
log.error("Corda service ${it.name} failed to instantiate. Reason was: ${e.cause?.rootMessage}", e.cause)
} else {
log.error("Corda service ${it.name} failed to instantiate", e)
}
} catch (e: Exception) {
log.error("Unable to install Corda service ${it.name}", e)
}
} catch (e: Exception) {
log.error("Unable to install Corda service ${it.name}", e)
}
} finally {
Thread.currentThread().contextClassLoader = oldContextClassLoader
}
}

View File

@ -26,10 +26,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlin.test.*
class CordaServiceTest {
private lateinit var mockNet: MockNetwork
@ -77,6 +74,12 @@ class CordaServiceTest {
service.startServiceFlowAndTrack()
}
@Test
fun `Corda service can access a non-null thread context classloader`() {
val service = nodeA.services.cordaService(CordaServiceThatRequiresThreadContextClassLoader::class.java)
service.thatWeCanAccessClassLoader()
}
/**
* Reproduce CORDA-2296
* Querying the vault from a services constructor failed because the criteriaBuilder
@ -142,4 +145,19 @@ class CordaServiceTest {
serviceHub.vaultService.trackBy(ContractState::class.java, criteria)
}
}
/**
* See: CORDA-2653
* This is to check that a corda service is presented with a non-null thread context classloader
*/
@CordaService
class CordaServiceThatRequiresThreadContextClassLoader(val serviceHub: AppServiceHub) : SingletonSerializeAsToken() {
init {
assertNotNull(Thread.currentThread().contextClassLoader, "thread context classloader should not be null during service initialisation")
}
fun thatWeCanAccessClassLoader() {
assertNotNull(Thread.currentThread().contextClassLoader, "thread context classloader should not be null during service initialisation")
}
}
}