Merge remote-tracking branch 'open/master' into aslemmer-enterprise-merge-september-8

This commit is contained in:
Andras Slemmer
2017-09-08 16:03:33 +01:00
36 changed files with 302 additions and 237 deletions

View File

@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutionException
val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled"
@ -42,19 +43,28 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build()
// We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context.
/**
* {@inheritDoc}
*
* We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context.
*/
override fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): SerializationContext {
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean ?: false || return this
val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContextImpl ?: return this // Some tests don't set one.
return withClassLoader(cache.get(attachmentHashes) {
val missing = ArrayList<SecureHash>()
val attachments = ArrayList<Attachment>()
attachmentHashes.forEach { id ->
serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id }
}
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
AttachmentsClassLoader(attachments)
})
try {
return withClassLoader(cache.get(attachmentHashes) {
val missing = ArrayList<SecureHash>()
val attachments = ArrayList<Attachment>()
attachmentHashes.forEach { id ->
serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id }
}
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
AttachmentsClassLoader(attachments)
})
} catch (e: ExecutionException) {
// Caught from within the cache get, so unwrap.
throw e.cause!!
}
}
override fun withProperty(property: Any, value: Any): SerializationContext {

View File

@ -15,7 +15,7 @@ import kotlin.reflect.jvm.javaConstructor
open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
override val type: Type get() = clazz
open val kotlinConstructor = constructorForDeserialization(clazz)
val javaConstructor by lazy { kotlinConstructor?.javaConstructor?.apply { isAccessible = true } }
val javaConstructor by lazy { kotlinConstructor?.javaConstructor }
private val logger = loggerFor<ObjectSerializer>()

View File

@ -13,6 +13,7 @@ import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaType
/**
@ -48,7 +49,9 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
preferredCandidate = kotlinConstructor
}
}
return preferredCandidate ?: throw NotSerializableException("No constructor for deserialization found for $clazz.")
return preferredCandidate?.apply { isAccessible = true}
?: throw NotSerializableException("No constructor for deserialization found for $clazz.")
} else {
return null
}

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.CordaRuntimeException
import net.corda.core.CordaThrowable
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import net.corda.nodeapi.internal.serialization.amqp.constructorForDeserialization
@ -9,6 +10,11 @@ import net.corda.nodeapi.internal.serialization.amqp.propertiesForSerialization
import java.io.NotSerializableException
class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Throwable, ThrowableSerializer.ThrowableProxy>(Throwable::class.java, ThrowableProxy::class.java, factory) {
companion object {
private val logger = loggerFor<ThrowableSerializer>()
}
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(StackTraceElementSerializer(factory))
override fun toProxy(obj: Throwable): ThrowableProxy {
@ -33,7 +39,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
override fun fromProxy(proxy: ThrowableProxy): Throwable {
try {
// TODO: This will need reworking when we have multiple class loaders
val clazz = Class.forName(proxy.exceptionClass, false, this.javaClass.classLoader)
val clazz = Class.forName(proxy.exceptionClass, false, factory.classloader)
// If it is CordaException or CordaRuntimeException, we can seek any constructor and then set the properties
// Otherwise we just make a CordaRuntimeException
if (CordaThrowable::class.java.isAssignableFrom(clazz) && Throwable::class.java.isAssignableFrom(clazz)) {
@ -50,7 +56,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
}
}
} catch (e: Exception) {
// If attempts to rebuild the exact exception fail, we fall through and build a runtime exception.
logger.warn("Unexpected exception de-serializing throwable: ${proxy.exceptionClass}. Converting to CordaRuntimeException.", e)
}
// If the criteria are not met or we experience an exception constructing the exception, we fall back to our own unchecked exception.
return CordaRuntimeException(proxy.exceptionClass).apply {

View File

@ -10,7 +10,6 @@ import net.corda.core.internal.declaredField
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ByteSequence
@ -377,4 +376,24 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
// Then deserialize with the attachment class loader associated with the attachment
serialized.deserialize(context = inboundContext)
}
@Test
fun `test loading a class with attachment missing during deserialization`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val storage = MockAttachmentStorage()
val attachmentRef = SecureHash.randomSHA256()
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
val e = assertFailsWith(MissingAttachmentsException::class) {
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(contract.javaClass).withAttachmentStorage(storage).withAttachmentsClassLoader(listOf(attachmentRef))
serialized.deserialize(context = inboundContext)
}
assertEquals(attachmentRef, e.ids.single())
}
}