Merge open master

This commit is contained in:
Andras Slemmer
2017-08-29 15:08:04 +01:00
526 changed files with 7849 additions and 7317 deletions

View File

@ -31,6 +31,9 @@ dependencies {
compile "com.esotericsoftware:kryo:4.0.0"
compile "de.javakaffee:kryo-serializers:0.41"
// For AMQP serialisation.
compile "org.apache.qpid:proton-j:0.19.0"
// Unit testing helpers.
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"

View File

@ -6,10 +6,8 @@ import com.esotericsoftware.kryo.Registration
import com.esotericsoftware.kryo.Serializer
import net.corda.core.concurrent.CordaFuture
import net.corda.core.CordaRuntimeException
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.toFuture
import net.corda.core.toObservable
import net.corda.nodeapi.config.OldConfig
@ -49,7 +47,7 @@ class PermissionException(msg: String) : RuntimeException(msg)
// The Kryo used for the RPC wire protocol. Every type in the wire protocol is listed here explicitly.
// This is annoying to write out, but will make it easier to formalise the wire protocol when the time comes,
// because we can see everything we're using in one place.
class RPCKryo(observableSerializer: Serializer<Observable<*>>, val serializationFactory: SerializationFactory, val serializationContext: SerializationContext) : CordaKryo(CordaClassResolver(serializationFactory, serializationContext)) {
class RPCKryo(observableSerializer: Serializer<Observable<*>>, serializationContext: SerializationContext) : CordaKryo(CordaClassResolver(serializationContext)) {
init {
DefaultKryoCustomizer.customize(this)

View File

@ -11,7 +11,7 @@ import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.util.concurrent.ConcurrentHashMap
internal val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferedSerializationVersion == AmqpHeaderV1_0
internal val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == AmqpHeaderV1_0
abstract class AbstractAMQPSerializationScheme : SerializationScheme {
internal companion object {
@ -22,7 +22,23 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
register(net.corda.nodeapi.internal.serialization.amqp.custom.X500NameSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.DurationSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalDateSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalDateTimeSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalTimeSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.ZonedDateTimeSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.ZoneIdSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.OffsetTimeSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.OffsetDateTimeSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.YearSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.YearMonthSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory))
}
}
}
@ -41,7 +57,7 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
rpcClientSerializerFactory(context)
SerializationContext.UseCase.RPCServer ->
rpcServerSerializerFactory(context)
else -> SerializerFactory(context.whitelist) // TODO pass class loader also
else -> SerializerFactory(context.whitelist, context.deserializationClassLoader)
}
}.also { registerCustomSerializers(it) }
}
@ -99,9 +115,3 @@ val AMQP_P2P_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val AMQP_STORAGE_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0,
SerializationDefaults.javaClass.classLoader,
AllButBlacklisted,
emptyMap(),
true,
SerializationContext.UseCase.Storage)

View File

@ -39,6 +39,9 @@ object AllButBlacklisted : ClassWhitelist {
Thread::class.java.name,
HashSet::class.java.name,
HashMap::class.java.name,
WeakHashMap::class.java.name,
Dictionary::class.java.name, // Deprecated (marked obsolete) in the jdk
Hashtable::class.java.name, // see [Dictionary]
ClassLoader::class.java.name,
Handler::class.java.name, // MemoryHandler, StreamHandler
Runtime::class.java.name,

View File

@ -0,0 +1,17 @@
@file:JvmName("ClientContexts")
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
/*
* Serialisation contexts for the client.
* These have been refactored into a separate file to prevent
* servers from trying to instantiate any of them.
*/
val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(),
true,
SerializationContext.UseCase.RPCClient)

View File

@ -8,7 +8,6 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.Util
import net.corda.core.serialization.*
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
import java.io.PrintWriter
import java.lang.reflect.Modifier.isAbstract
import java.nio.charset.StandardCharsets
@ -22,10 +21,9 @@ fun Kryo.addToWhitelist(type: Class<*>) {
}
/**
* @param amqpEnabled Setting this to true turns on experimental AMQP serialization for any class annotated with
* [CordaSerializable].
* Corda specific class resolver which enables extra customisation for the purposes of serialization using Kryo
*/
class CordaClassResolver(val serializationFactory: SerializationFactory, val serializationContext: SerializationContext) : DefaultClassResolver() {
class CordaClassResolver(serializationContext: SerializationContext) : DefaultClassResolver() {
val whitelist: ClassWhitelist = TransientClassWhiteList(serializationContext.whitelist)
/** Returns the registration for the specified class, or null if the class is not registered. */
@ -64,13 +62,6 @@ class CordaClassResolver(val serializationFactory: SerializationFactory, val ser
}
override fun registerImplicit(type: Class<*>): Registration {
// If something is not annotated, or AMQP is disabled, we stay serializing with Kryo. This will typically be the
// case for flow checkpoints (ignoring all cases where AMQP is disabled) since our top level messaging data structures
// are annotated and once we enter AMQP serialisation we stay with it for the entire object subgraph.
if (checkForAnnotation(type) && AMQP_ENABLED) {
// Build AMQP serializer
return register(Registration(type, KryoAMQPSerializer(serializationFactory, serializationContext), NAME.toInt()))
}
val objectInstance = try {
type.kotlin.objectInstance
@ -82,13 +73,12 @@ class CordaClassResolver(val serializationFactory: SerializationFactory, val ser
val references = kryo.references
try {
kryo.references = true
val serializer = if (objectInstance != null) {
KotlinObjectSerializer(objectInstance)
} else if (kotlin.jvm.internal.Lambda::class.java.isAssignableFrom(type)) {
// Kotlin lambdas extend this class and any captured variables are stored in synthentic fields
FieldSerializer<Any>(kryo, type).apply { setIgnoreSyntheticFields(false) }
} else {
kryo.getDefaultSerializer(type)
val serializer = when {
objectInstance != null -> KotlinObjectSerializer(objectInstance)
kotlin.jvm.internal.Lambda::class.java.isAssignableFrom(type) -> // Kotlin lambdas extend this class and any captured variables are stored in synthetic fields
FieldSerializer<Any>(kryo, type).apply { setIgnoreSyntheticFields(false) }
Throwable::class.java.isAssignableFrom(type) -> ThrowableSerializer(kryo, type)
else -> kryo.getDefaultSerializer(type)
}
return register(Registration(type, serializer, NAME.toInt()))
} finally {
@ -125,11 +115,9 @@ class CordaClassResolver(val serializationFactory: SerializationFactory, val ser
// TODO: come up with a more efficient way. e.g. segregate the name space by class loader.
if (nameToClass != null) {
val classesToRemove: MutableList<String> = ArrayList(nameToClass.size)
for (entry in nameToClass.entries()) {
if (entry.value.classLoader is AttachmentsClassLoader) {
classesToRemove += entry.key
}
}
nameToClass.entries()
.filter { it.value.classLoader is AttachmentsClassLoader }
.forEach { classesToRemove += it.key }
for (className in classesToRemove) {
nameToClass.remove(className)
}
@ -169,7 +157,7 @@ class GlobalTransientClassWhiteList(val delegate: ClassWhitelist) : MutableClass
}
/**
* A whitelist that can be customised via the [CordaPluginRegistry], since implements [MutableClassWhitelist].
* A whitelist that can be customised via the [net.corda.core.node.CordaPluginRegistry], since implements [MutableClassWhitelist].
*/
class TransientClassWhiteList(val delegate: ClassWhitelist) : MutableClassWhitelist, ClassWhitelist by delegate {
val whitelist: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())

View File

@ -6,12 +6,13 @@ import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.MapReferenceResolver
import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.BitSetSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes
@ -59,6 +60,12 @@ object DefaultKryoCustomizer {
instantiatorStrategy = CustomInstantiatorStrategy()
// Required for HashCheckingStream (de)serialization.
// Note that return type should be specifically set to InputStream, otherwise it may not work, i.e. val aStream : InputStream = HashCheckingStream(...).
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
addDefaultSerializer(Logger::class.java, LoggerSerializer)
// WARNING: reordering the registrations here will cause a change in the serialized form, since classes
// with custom serializers get written as registration ids. This will break backwards-compatibility.
// Please add any new registrations to the end.
@ -68,50 +75,31 @@ object DefaultKryoCustomizer {
register(SignedTransaction::class.java, SignedTransactionSerializer)
register(WireTransaction::class.java, WireTransactionSerializer)
register(SerializedBytes::class.java, SerializedBytesSerializer)
UnmodifiableCollectionsSerializer.registerSerializers(this)
ImmutableListSerializer.registerSerializers(this)
ImmutableSetSerializer.registerSerializers(this)
ImmutableSortedSetSerializer.registerSerializers(this)
ImmutableMapSerializer.registerSerializers(this)
ImmutableMultimapSerializer.registerSerializers(this)
// InputStream subclasses whitelisting, required for attachments.
register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
noReferencesWithin<WireTransaction>()
register(ECPublicKeyImpl::class.java, ECPublicKeyImplSerializer)
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
// Using a custom serializer for compactness
register(CompositeKey::class.java, CompositeKeySerializer)
register(CompositeKey::class.java, CompositeKeySerializer) // Using a custom serializer for compactness
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
register(Array<StackTraceElement>::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> })
// This ensures a NonEmptySetSerializer is constructed with an initial value.
register(NonEmptySet::class.java, NonEmptySetSerializer)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
register(BitSet::class.java, BitSetSerializer())
register(Class::class.java, ClassSerializer)
addDefaultSerializer(Logger::class.java, LoggerSerializer)
register(FileInputStream::class.java, InputStreamSerializer)
// Required for HashCheckingStream (de)serialization.
// Note that return type should be specifically set to InputStream, otherwise it may not work, i.e. val aStream : InputStream = HashCheckingStream(...).
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
register(CertPath::class.java, CertPathSerializer)
register(X509CertPath::class.java, CertPathSerializer)
register(X500Name::class.java, X500NameSerializer)
register(X509CertificateHolder::class.java, X509CertificateSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, PublicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
@ -119,8 +107,11 @@ object DefaultKryoCustomizer {
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
register(BCSphincs256PublicKey::class.java, PublicKeySerializer)
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer)
register(PartyAndCertificate::class.java, PartyAndCertificateSerializer)
// Don't deserialize PrivacySalt via its default constructor.
register(PrivacySalt::class.java, PrivacySaltSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
@ -139,6 +130,15 @@ object DefaultKryoCustomizer {
}
}
private object PartyAndCertificateSerializer : Serializer<PartyAndCertificate>() {
override fun write(kryo: Kryo, output: Output, obj: PartyAndCertificate) {
kryo.writeClassAndObject(output, obj.certPath)
}
override fun read(kryo: Kryo, input: Input, type: Class<PartyAndCertificate>): PartyAndCertificate {
return PartyAndCertificate(kryo.readClassAndObject(input) as CertPath)
}
}
private object NonEmptySetSerializer : Serializer<NonEmptySet<Any>>() {
override fun write(kryo: Kryo, output: Output, obj: NonEmptySet<Any>) {
// Write out the contents as normal
@ -156,4 +156,18 @@ object DefaultKryoCustomizer {
return list.toNonEmptySet()
}
}
}
/*
* Avoid deserialising PrivacySalt via its default constructor
* because the random number generator may not be available.
*/
private object PrivacySaltSerializer : Serializer<PrivacySalt>() {
override fun write(kryo: Kryo, output: Output, obj: PrivacySalt) {
output.writeBytesWithLength(obj.bytes)
}
override fun read(kryo: Kryo, input: Input, type: Class<PrivacySalt>): PrivacySalt {
return PrivacySalt(input.readBytesWithLength())
}
}
}

View File

@ -1,8 +1,11 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.contracts.*
import net.corda.core.crypto.Crypto
@ -569,4 +572,46 @@ object X509CertificateSerializer : Serializer<X509CertificateHolder>() {
}
}
fun Kryo.serializationContext(): SerializeAsTokenContext? = context.get(serializationContextKey) as? SerializeAsTokenContext
fun Kryo.serializationContext(): SerializeAsTokenContext? = context.get(serializationContextKey) as? SerializeAsTokenContext
/**
* For serializing instances if [Throwable] honoring the fact that [java.lang.Throwable.suppressedExceptions]
* might be un-initialized/empty.
* In the absence of this class [CompatibleFieldSerializer] will be used which will assign a *new* instance of
* unmodifiable collection to [java.lang.Throwable.suppressedExceptions] which will fail some sentinel identity checks
* e.g. in [java.lang.Throwable.addSuppressed]
*/
@ThreadSafe
class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>(false, true) {
private companion object {
private val suppressedField = Throwable::class.java.getDeclaredField("suppressedExceptions")
private val sentinelValue = let {
val sentinelField = Throwable::class.java.getDeclaredField("SUPPRESSED_SENTINEL")
sentinelField.isAccessible = true
sentinelField.get(null)
}
init {
suppressedField.isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val delegate: Serializer<Throwable> = ReflectionSerializerFactory.makeSerializer(kryo, FieldSerializer::class.java, type) as Serializer<Throwable>
override fun write(kryo: Kryo, output: Output, throwable: Throwable) {
delegate.write(kryo, output, throwable)
}
override fun read(kryo: Kryo, input: Input, type: Class<Throwable>): Throwable {
val throwableRead = delegate.read(kryo, input, type)
if(throwableRead.suppressed.isEmpty()) {
throwableRead.setSuppressedToSentinel()
}
return throwableRead
}
private fun Throwable.setSuppressedToSentinel() = suppressedField.set(this, sentinelValue)
}

View File

@ -1,37 +0,0 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.sequence
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
/**
* This [Kryo] custom [Serializer] switches the object graph of anything annotated with `@CordaSerializable`
* to using the AMQP serialization wire format, and simply writes that out as bytes to the wire.
*
* There is no need to write out the length, since this can be peeked out of the first few bytes of the stream.
*/
class KryoAMQPSerializer(val serializationFactory: SerializationFactory, val serializationContext: SerializationContext) : Serializer<Any>() {
override fun write(kryo: Kryo, output: Output, obj: Any) {
val bytes = serializationFactory.serialize(obj, serializationContext.withPreferredSerializationVersion(AmqpHeaderV1_0)).bytes
// No need to write out the size since it's encoded within the AMQP.
output.write(bytes)
}
override fun read(kryo: Kryo, input: Input, type: Class<Any>): Any {
// Use our helper functions to peek the size of the serialized object out of the AMQP byte stream.
val peekedBytes = input.readBytes(DeserializationInput.BYTES_NEEDED_TO_PEEK)
val size = DeserializationInput.peekSize(peekedBytes)
val allBytes = peekedBytes.copyOf(size)
input.readBytes(allBytes, peekedBytes.size, size - peekedBytes.size)
return serializationFactory.deserialize(allBytes.sequence(), type, serializationContext)
}
}

View File

@ -8,7 +8,6 @@ import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.pool.KryoPool
import io.requery.util.CloseableIterator
import net.corda.core.internal.LazyPool
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
@ -28,7 +27,7 @@ object NotSupportedSeralizationScheme : SerializationScheme {
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> = doThrow()
}
data class SerializationContextImpl(override val preferedSerializationVersion: ByteSequence,
data class SerializationContextImpl(override val preferredSerializationVersion: ByteSequence,
override val deserializationClassLoader: ClassLoader,
override val whitelist: ClassWhitelist,
override val properties: Map<Any, Any>,
@ -52,7 +51,7 @@ data class SerializationContextImpl(override val preferedSerializationVersion: B
})
}
override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferedSerializationVersion = versionHeader)
override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferredSerializationVersion = versionHeader)
}
private const val HEADER_SIZE: Int = 8
@ -68,11 +67,9 @@ open class SerializationFactoryImpl : SerializationFactory {
private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme {
// truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays
return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) {
for (scheme in registeredSchemes) {
if (scheme.canDeserializeVersion(it.first, it.second)) {
return@computeIfAbsent scheme
}
}
registeredSchemes
.filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) }
.forEach { return@computeIfAbsent it }
NotSupportedSeralizationScheme
}
}
@ -81,7 +78,7 @@ open class SerializationFactoryImpl : SerializationFactory {
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T = schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context)
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
return schemeFor(context.preferedSerializationVersion, context.useCase).serialize(obj, context)
return schemeFor(context.preferredSerializationVersion, context.useCase).serialize(obj, context)
}
fun registerScheme(scheme: SerializationScheme) {
@ -105,21 +102,16 @@ open class SerializationFactoryImpl : SerializationFactory {
private object AutoCloseableSerialisationDetector : Serializer<AutoCloseable>() {
override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) {
val message = if (closeable is CloseableIterator<*>) {
"A live Iterator pointing to the database has been detected during flow checkpointing. This may be due " +
"to a Vault query - move it into a private method."
} else {
"${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " +
val message = "${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " +
"Restoring such resources across node restarts is not supported. Make sure code accessing it is " +
"confined to a private method or the reference is nulled out."
}
throw UnsupportedOperationException(message)
}
override fun read(kryo: Kryo, input: Input, type: Class<AutoCloseable>) = throw IllegalStateException("Should not reach here!")
}
abstract class AbstractKryoSerializationScheme(val serializationFactory: SerializationFactory) : SerializationScheme {
abstract class AbstractKryoSerializationScheme : SerializationScheme {
private val kryoPoolsForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, KryoPool>()
protected abstract fun rpcClientKryoPool(context: SerializationContext): KryoPool
@ -131,7 +123,7 @@ abstract class AbstractKryoSerializationScheme(val serializationFactory: Seriali
SerializationContext.UseCase.Checkpoint ->
KryoPool.Builder {
val serializer = Fiber.getFiberSerializer(false) as KryoSerializer
val classResolver = CordaClassResolver(serializationFactory, context).apply { setKryo(serializer.kryo) }
val classResolver = CordaClassResolver(context).apply { setKryo(serializer.kryo) }
// TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that
val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true }
serializer.kryo.apply {
@ -147,7 +139,7 @@ abstract class AbstractKryoSerializationScheme(val serializationFactory: Seriali
rpcServerKryoPool(context)
else ->
KryoPool.Builder {
DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(serializationFactory, context))).apply { classLoader = it.second }
DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(context))).apply { classLoader = it.second }
}.build()
}
}
@ -226,34 +218,6 @@ val KRYO_P2P_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(),
true,
SerializationContext.UseCase.RPCServer)
val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(),
true,
SerializationContext.UseCase.RPCClient)
val KRYO_STORAGE_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
AllButBlacklisted,
emptyMap(),
true,
SerializationContext.UseCase.Storage)
val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
QuasarWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.Checkpoint)
object QuasarWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = true
}
interface SerializationScheme {
// byteSequence expected to just be the 8 bytes necessary for versioning

View File

@ -0,0 +1,46 @@
@file:JvmName("ServerContexts")
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
/*
* Serialisation contexts for the server.
* These have been refactored into a separate file to prevent
* clients from trying to instantiate any of them.
*
* NOTE: The [KRYO_STORAGE_CONTEXT] and [AMQP_STORAGE_CONTEXT]
* CANNOT always be instantiated outside of the server and so
* MUST be kept separate!
*/
val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(),
true,
SerializationContext.UseCase.RPCServer)
val KRYO_STORAGE_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
AllButBlacklisted,
emptyMap(),
true,
SerializationContext.UseCase.Storage)
val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1,
SerializationDefaults.javaClass.classLoader,
QuasarWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.Checkpoint)
object QuasarWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = true
}
val AMQP_STORAGE_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0,
SerializationDefaults.javaClass.classLoader,
AllButBlacklisted,
emptyMap(),
true,
SerializationContext.UseCase.Storage)

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.NonEmptySet
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
@ -22,7 +23,8 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
List::class.java to { list -> Collections.unmodifiableList(list) },
Set::class.java to { list -> Collections.unmodifiableSet(LinkedHashSet(list)) },
SortedSet::class.java to { list -> Collections.unmodifiableSortedSet(TreeSet(list)) },
NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) }
NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) },
NonEmptySet::class.java to { list -> NonEmptySet.copyOf(list) }
)
private fun findConcreteType(clazz: Class<*>): (List<*>) -> Collection<*> {

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializationDefaults
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
@ -13,7 +14,7 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* This is a collection of custom serializers that this custom serializer depends on. e.g. for proxy objects
* that refer to other custom types etc.
*/
abstract val additionalSerializers: Iterable<CustomSerializer<out Any>>
open val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
/**
* This method should return true if the custom serializer can serialize an instance of the class passed as the
@ -44,7 +45,6 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
*/
// TODO: should this be a custom serializer at all, or should it just be a plain AMQPSerializer?
class SubClass<T>(protected val clazz: Class<*>, protected val superClassSerializer: CustomSerializer<T>) : CustomSerializer<T>() {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
// TODO: should this be empty or contain the schema of the super?
override val schemaForDocumentation = Schema(emptyList())
@ -68,26 +68,26 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
}
/**
* Additional base features for a custom serializer for a particular class, that excludes subclasses.
* Additional base features for a custom serializer for a particular class [withInheritance] is false
* or super class / interfaces [withInheritance] is true
*/
abstract class Is<T>(protected val clazz: Class<T>) : CustomSerializer<T>() {
override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == this.clazz
abstract class CustomSerializerImp<T>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() {
override val type: Type get() = clazz
override val typeDescriptor: String = "$DESCRIPTOR_DOMAIN:${nameForType(clazz)}"
override fun writeClassInfo(output: SerializationOutput) {}
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override fun isSerializerFor(clazz: Class<*>): Boolean = if (withInheritance) this.clazz.isAssignableFrom(clazz) else this.clazz == clazz
}
/**
* Additional base features for a custom serializer for a particular class, that excludes subclasses.
*/
abstract class Is<T>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, false)
/**
* Additional base features for a custom serializer for all implementations of a particular interface or super class.
*/
abstract class Implements<T>(protected val clazz: Class<T>) : CustomSerializer<T>() {
override fun isSerializerFor(clazz: Class<*>): Boolean = this.clazz.isAssignableFrom(clazz)
override val type: Type get() = clazz
override val typeDescriptor: String = "$DESCRIPTOR_DOMAIN:${nameForType(clazz)}"
override fun writeClassInfo(output: SerializationOutput) {}
override val descriptor: Descriptor = Descriptor(typeDescriptor)
}
abstract class Implements<T>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, true)
/**
* Additional base features over and above [Implements] or [Is] custom serializer for when the serialized form should be
@ -96,15 +96,11 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* The proxy class must use only types which are either native AMQP or other types for which there are pre-registered
* custom serializers.
*/
abstract class Proxy<T, P>(protected val clazz: Class<T>,
abstract class Proxy<T, P>(clazz: Class<T>,
protected val proxyClass: Class<P>,
protected val factory: SerializerFactory,
val withInheritance: Boolean = true) : CustomSerializer<T>() {
withInheritance: Boolean = true) : CustomSerializerImp<T>(clazz, withInheritance) {
override fun isSerializerFor(clazz: Class<*>): Boolean = if (withInheritance) this.clazz.isAssignableFrom(clazz) else this.clazz == clazz
override val type: Type get() = clazz
override val typeDescriptor: String = "$DESCRIPTOR_DOMAIN:${nameForType(clazz)}"
override fun writeClassInfo(output: SerializationOutput) {}
override val descriptor: Descriptor = Descriptor(typeDescriptor)
private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(proxyClass, factory) }
@ -151,25 +147,25 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* @param unmake A lambda that extracts the string value for an instance, that defaults to the [toString] method.
*/
abstract class ToString<T>(clazz: Class<T>, withInheritance: Boolean = false,
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` -> { string -> `constructor`.newInstance(string) } },
private val unmaker: (T) -> String = { obj -> obj.toString() }) : Proxy<T, String>(clazz, String::class.java, /* Unused */ SerializerFactory(), withInheritance) {
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let {
`constructor` ->
{ string -> `constructor`.newInstance(string) }
},
private val unmaker: (T) -> String = { obj -> obj.toString() })
: CustomSerializerImp<T>(clazz, withInheritance) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
override val schemaForDocumentation = Schema(listOf(RestrictedType(nameForType(type), "", listOf(nameForType(type)), SerializerFactory.primitiveTypeName(String::class.java)!!, descriptor, emptyList())))
override fun toProxy(obj: T): String = unmaker(obj)
override fun fromProxy(proxy: String): T = maker(proxy)
override val schemaForDocumentation = Schema(
listOf(RestrictedType(nameForType(type), "", listOf(nameForType(type)),
SerializerFactory.primitiveTypeName(String::class.java)!!,
descriptor, emptyList())))
override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) {
val proxy = toProxy(obj)
data.putObject(proxy)
data.putObject(unmaker(obj))
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): T {
val proxy = input.readObject(obj, schema, String::class.java) as String
return fromProxy(proxy)
return maker(proxy)
}
}
}

View File

@ -8,6 +8,7 @@ import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.amqp.UnsignedByte
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.nio.ByteBuffer
import java.util.*
@ -20,7 +21,7 @@ data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope)
* @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple
* instances and threads.
*/
class DeserializationInput(internal val serializerFactory: SerializerFactory = SerializerFactory()) {
class DeserializationInput(internal val serializerFactory: SerializerFactory) {
// TODO: we're not supporting object refs yet
private val objectHistory: MutableList<Any> = ArrayList()
@ -118,7 +119,7 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory = S
if (obj is DescribedType) {
// Look up serializer in factory by descriptor
val serializer = serializerFactory.get(obj.descriptor, schema)
if (serializer.type != type && !serializer.type.isSubClassOf(type))
if (serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) })
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
"expected to be of type $type but was ${serializer.type}")
return serializer.readObject(obj.described, schema, this)
@ -128,4 +129,12 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory = S
return obj
}
}
}
/**
* TODO: Currently performs rather basic checks aimed in particular at [java.util.List<Command<?>>] and
* [java.lang.Class<? extends net.corda.core.contracts.Contract>]
* In the future tighter control might be needed
*/
private fun Type.materiallyEquivalentTo(that: Type): Boolean =
asClass() == that.asClass() && that is ParameterizedType
}

View File

@ -19,25 +19,17 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
if (params.size != rawType.typeParameters.size) {
throw NotSerializableException("Expected ${rawType.typeParameters.size} for ${rawType.name} but found ${params.size}")
}
// We do not check bounds. Both our use cases (Collection and Map) are not bounded.
if (rawType.typeParameters.any { boundedType(it) }) throw NotSerializableException("Bounded types in ParameterizedTypes not supported, but found a bound in $rawType")
}
private fun boundedType(type: TypeVariable<out Class<out Any>>): Boolean {
return !(type.bounds.size == 1 && type.bounds[0] == Object::class.java)
}
val isFullyWildcarded: Boolean = params.all { it == SerializerFactory.AnyType }
private val _typeName: String = makeTypeName()
private fun makeTypeName(): String {
return if (isFullyWildcarded) {
rawType.name
} else {
val paramsJoined = params.map { it.typeName }.joinToString(", ")
"${rawType.name}<$paramsJoined>"
}
val paramsJoined = params.map { it.typeName }.joinToString(", ")
return "${rawType.name}<$paramsJoined>"
}
companion object {
@ -96,7 +88,7 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
typeStart = pos++
} else if (!needAType) {
throw NotSerializableException("Not expecting a type")
} else if (params[pos] == '*') {
} else if (params[pos] == '?') {
pos++
} else if (!params[pos].isJavaIdentifierStart()) {
throw NotSerializableException("Invalid character at start of type: ${params[pos]}")

View File

@ -0,0 +1,130 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.nodeapi.internal.serialization.carpenter.getTypeAsClass
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.io.NotSerializableException
import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaType
/**
* Serializer for deserialising objects whose definition has changed since they
* were serialised
*/
class EvolutionSerializer(
clazz: Type,
factory: SerializerFactory,
val readers: List<OldParam?>,
override val kotlinConstructor: KFunction<Any>?) : ObjectSerializer(clazz, factory) {
// explicitly set as empty to indicate it's unused by this type of serializer
override val propertySerializers: Collection<PropertySerializer> = emptyList()
/**
* Represents a parameter as would be passed to the constructor of the class as it was
* when it was serialised and NOT how that class appears now
*
* @param type The jvm type of the parameter
* @param idx where in the parameter list this parameter falls. Required as the parameter
* order may have been changed and we need to know where into the list to look
* @param property object to read the actual property value
*/
data class OldParam(val type: Type, val idx: Int, val property: PropertySerializer) {
fun readProperty(paramValues: List<*>, schema: Schema, input: DeserializationInput) =
property.readProperty(paramValues[idx], schema, input)
}
companion object {
/**
* Unlike the generic deserialisation case where we need to locate the primary constructor
* for the object (or our best guess) in the case of an object whose structure has changed
* since serialisation we need to attempt to locate a constructor that we can use. I.e.
* it's parameters match the serialised members and it will initialise any newly added
* elements
*
* TODO: Type evolution
* TODO: rename annotation
*/
internal fun getEvolverConstructor(type: Type, oldArgs: Map<String?, Type>): KFunction<Any>? {
val clazz: Class<*> = type.asClass()!!
if (!isConcrete(clazz)) return null
val oldArgumentSet = oldArgs.map { Pair(it.key, it.value) }
var maxConstructorVersion = Integer.MIN_VALUE
var constructor: KFunction<Any>? = null
clazz.kotlin.constructors.forEach {
val version = it.findAnnotation<DeprecatedConstructorForDeserialization>()?.version ?: Integer.MIN_VALUE
if (oldArgumentSet.containsAll(it.parameters.map { v -> Pair(v.name, v.type.javaType) }) &&
version > maxConstructorVersion) {
constructor = it
maxConstructorVersion = version
}
}
// if we didn't get an exact match revert to existing behaviour, if the new parameters
// are not mandatory (i.e. nullable) things are fine
return constructor ?: constructorForDeserialization(type)
}
/**
* Build a serialization object for deserialisation only of objects serialised
* as different versions of a class
*
* @param old is an object holding the schema that represents the object
* as it was serialised and the type descriptor of that type
* @param new is the Serializer built for the Class as it exists now, not
* how it was serialised and persisted.
*/
fun make(old: CompositeType, new: ObjectSerializer,
factory: SerializerFactory): AMQPSerializer<Any> {
val oldFieldToType = old.fields.map {
it.name as String? to it.getTypeAsClass(factory.classloader) as Type
}.toMap()
val constructor = getEvolverConstructor(new.type, oldFieldToType) ?:
throw NotSerializableException(
"Attempt to deserialize an interface: ${new.type}. Serialized form is invalid.")
val oldArgs = mutableMapOf<String, OldParam>()
var idx = 0
old.fields.forEach {
val returnType = it.getTypeAsClass(factory.classloader)
oldArgs[it.name] = OldParam(
returnType, idx++, PropertySerializer.make(it.name, null, returnType, factory))
}
val readers = constructor.parameters.map {
oldArgs[it.name!!] ?: if (!it.type.isMarkedNullable) {
throw NotSerializableException(
"New parameter ${it.name} is mandatory, should be nullable for evolution to worK")
} else null
}
return EvolutionSerializer(new.type, factory, readers, constructor)
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
throw IllegalAccessException("It should be impossible to write an evolution serializer")
}
/**
* Unlike a normal [readObject] call where we simply apply the parameter deserialisers
* to the object list of values we need to map that list, which is ordered per the
* constructor of the original state of the object, we need to map the new parameter order
* of the current constructor onto that list inserting nulls where new parameters are
* encountered
*
* TODO: Object references
*/
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj")
return construct(readers.map { it?.readProperty(obj, schema, input) })
}
}

View File

@ -5,6 +5,7 @@ import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.*
import kotlin.collections.LinkedHashMap
import kotlin.collections.Map
import kotlin.collections.iterator
import kotlin.collections.map
@ -17,12 +18,15 @@ class MapSerializer(val declaredType: ParameterizedType, factory: SerializerFact
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
companion object {
private val supportedTypes: Map<Class<out Map<*, *>>, (Map<*, *>) -> Map<*, *>> = mapOf(
private val supportedTypes: Map<Class<out Any?>, (Map<*, *>) -> Map<*, *>> = mapOf(
// Interfaces
Map::class.java to { map -> Collections.unmodifiableMap(map) },
SortedMap::class.java to { map -> Collections.unmodifiableSortedMap(TreeMap(map)) },
NavigableMap::class.java to { map -> Collections.unmodifiableNavigableMap(TreeMap(map)) }
NavigableMap::class.java to { map -> Collections.unmodifiableNavigableMap(TreeMap(map)) },
// concrete classes for user convenience
LinkedHashMap::class.java to { map -> LinkedHashMap(map) },
TreeMap::class.java to { map -> TreeMap(map) }
)
private fun findConcreteType(clazz: Class<*>): (Map<*, *>) -> Map<*, *> {
return supportedTypes[clazz] ?: throw NotSerializableException("Unsupported map type $clazz.")
}
@ -40,7 +44,7 @@ class MapSerializer(val declaredType: ParameterizedType, factory: SerializerFact
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
obj.javaClass.checkNotUnorderedHashMap()
obj.javaClass.checkNotUnsupportedHashMap()
// Write described
data.withDescribed(typeNotation.descriptor) {
// Write map
@ -65,8 +69,17 @@ class MapSerializer(val declaredType: ParameterizedType, factory: SerializerFact
input.readObjectOrNull(entry.value, schema, declaredType.actualTypeArguments[1])
}
internal fun Class<*>.checkNotUnorderedHashMap() {
internal fun Class<*>.checkNotUnsupportedHashMap() {
if (HashMap::class.java.isAssignableFrom(this) && !LinkedHashMap::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException("Map type $this is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
throw IllegalArgumentException(
"Map type $this is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
}
else if (WeakHashMap::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException ("Weak references with map types not supported. Suggested fix: "
+ "use java.util.LinkedHashMap instead.")
}
else if (Dictionary::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException (
"Unable to serialise deprecated type $this. Suggested fix: prefer java.util.map implementations")
}
}

View File

@ -4,30 +4,27 @@ import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion
import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.Constructor
import java.lang.reflect.Type
import kotlin.reflect.jvm.javaConstructor
/**
* Responsible for serializing and deserializing a regular object instance via a series of properties (matched with a constructor).
*/
class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
override val type: Type get() = clazz
private val javaConstructor: Constructor<Any>?
internal val propertySerializers: Collection<PropertySerializer>
open val kotlinConstructor = constructorForDeserialization(clazz)
val javaConstructor by lazy { kotlinConstructor?.javaConstructor }
init {
val kotlinConstructor = constructorForDeserialization(clazz)
javaConstructor = kotlinConstructor?.javaConstructor
propertySerializers = propertiesForSerialization(kotlinConstructor, clazz, factory)
open internal val propertySerializers: Collection<PropertySerializer> by lazy {
propertiesForSerialization(kotlinConstructor, clazz, factory)
}
private val typeName = nameForType(clazz)
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
private val interfaces = interfacesForSerialization(clazz) // TODO maybe this proves too much and we need annotations to restrict.
private val interfaces = interfacesForSerialization(clazz, factory) // We restrict to only those annotated or whitelisted
internal val typeNotation: TypeNotation = CompositeType(typeName, null, generateProvides(), Descriptor(typeDescriptor, null), generateFields())
open internal val typeNotation : TypeNotation by lazy {CompositeType(typeName, null, generateProvides(), Descriptor(typeDescriptor, null), generateFields()) }
override fun writeClassInfo(output: SerializationOutput) {
if (output.writeTypeNotations(typeNotation)) {
@ -67,15 +64,8 @@ class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerial
return propertySerializers.map { Field(it.name, it.type, it.requires, it.default, null, it.mandatory, false) }
}
private fun generateProvides(): List<String> {
return interfaces.map { nameForType(it) }
}
private fun generateProvides(): List<String> = interfaces.map { nameForType(it) }
fun construct(properties: List<Any?>): Any {
if (javaConstructor == null) {
fun construct(properties: List<Any?>) = javaConstructor?.newInstance(*properties.toTypedArray()) ?:
throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.")
}
return javaConstructor.newInstance(*properties.toTypedArray())
}
}

View File

@ -10,7 +10,7 @@ import kotlin.reflect.jvm.javaGetter
/**
* Base class for serialization of a property of an object.
*/
sealed class PropertySerializer(val name: String, val readMethod: Method, val resolvedType: Type) {
sealed class PropertySerializer(val name: String, val readMethod: Method?, val resolvedType: Type) {
abstract fun writeClassInfo(output: SerializationOutput)
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
abstract fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any?
@ -44,7 +44,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
}
private fun generateMandatory(): Boolean {
return isJVMPrimitive || !readMethod.returnsNullable()
return isJVMPrimitive || !(readMethod?.returnsNullable() ?: true)
}
private fun Method.returnsNullable(): Boolean {
@ -53,7 +53,8 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
}
companion object {
fun make(name: String, readMethod: Method, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
readMethod?.isAccessible = true
if (SerializerFactory.isPrimitive(resolvedType)) {
return when(resolvedType) {
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
@ -68,7 +69,10 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
/**
* A property serializer for a complex type (another object).
*/
class DescribedTypePropertySerializer(name: String, readMethod: Method, resolvedType: Type, private val lazyTypeSerializer: () -> AMQPSerializer<*>) : PropertySerializer(name, readMethod, resolvedType) {
class DescribedTypePropertySerializer(
name: String, readMethod: Method?,
resolvedType: Type,
private val lazyTypeSerializer: () -> AMQPSerializer<*>) : PropertySerializer(name, readMethod, resolvedType) {
// This is lazy so we don't get an infinite loop when a method returns an instance of the class.
private val typeSerializer: AMQPSerializer<*> by lazy { lazyTypeSerializer() }
@ -83,14 +87,14 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
output.writeObjectOrNull(readMethod.invoke(obj), data, resolvedType)
output.writeObjectOrNull(readMethod!!.invoke(obj), data, resolvedType)
}
}
/**
* A property serializer for most AMQP primitive type (Int, String, etc).
*/
class AMQPPrimitivePropertySerializer(name: String, readMethod: Method, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
class AMQPPrimitivePropertySerializer(name: String, readMethod: Method?, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
override fun writeClassInfo(output: SerializationOutput) {}
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? {
@ -98,7 +102,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
val value = readMethod.invoke(obj)
val value = readMethod!!.invoke(obj)
if (value is ByteArray) {
data.putObject(Binary(value))
} else {
@ -112,7 +116,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
* value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit
* casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs
*/
class AMQPCharPropertySerializer(name: String, readMethod: Method) :
class AMQPCharPropertySerializer(name: String, readMethod: Method?) :
PropertySerializer(name, readMethod, Character::class.java) {
override fun writeClassInfo(output: SerializationOutput) {}
@ -121,7 +125,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
val input = readMethod.invoke(obj)
val input = readMethod!!.invoke(obj)
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
}
}

View File

@ -4,15 +4,13 @@ import com.google.common.hash.Hasher
import com.google.common.hash.Hashing
import net.corda.core.crypto.toBase64
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.loggerFor
import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.amqp.UnsignedLong
import org.apache.qpid.proton.codec.Data
import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException
import java.lang.reflect.GenericArrayType
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
import java.lang.reflect.*
import java.util.*
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
@ -316,6 +314,9 @@ private val NULLABLE_HASH: String = "Nullable = true"
private val NOT_NULLABLE_HASH: String = "Nullable = false"
private val ANY_TYPE_HASH: String = "Any type = true"
private val TYPE_VARIABLE_HASH: String = "Type variable = true"
private val WILDCARD_TYPE_HASH: String = "Wild card = true"
private val logger by lazy { loggerFor<Schema>() }
/**
* The method generates a fingerprint for a given JVM [Type] that should be unique to the schema representation.
@ -338,6 +339,16 @@ internal fun fingerprintForDescriptors(vararg typeDescriptors: String): String {
return hasher.hash().asBytes().toBase64()
}
private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFactory, clazz: Class<*>, declaredType: Type, block: () -> Hasher) : Hasher {
// Need to check if a custom serializer is applicable
val customSerializer = factory.findCustomSerializer(clazz, declaredType)
return if (customSerializer != null) {
putUnencodedChars(customSerializer.typeDescriptor)
} else {
block()
}
}
// This method concatentates various elements of the types recursively as unencoded strings into the hasher, effectively
// creating a unique string for a type which we then hash in the calling function above.
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
@ -356,18 +367,14 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
} else if (isCollectionOrMap(type)) {
hasher.putUnencodedChars(type.name)
} else {
// Need to check if a custom serializer is applicable
val customSerializer = factory.findCustomSerializer(type, type)
if (customSerializer == null) {
hasher.fingerprintWithCustomSerializerOrElse(factory, type, type) {
if (type.kotlin.objectInstance != null) {
// TODO: name collision is too likely for kotlin objects, we need to introduce some reference
// to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)
} else {
fingerprintForObject(type, contextType, alreadySeen, hasher, factory)
fingerprintForObject(type, type, alreadySeen, hasher, factory)
}
} else {
hasher.putUnencodedChars(customSerializer.typeDescriptor)
}
}
} else if (type is ParameterizedType) {
@ -376,7 +383,9 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
val startingHash = if (isCollectionOrMap(clazz)) {
hasher.putUnencodedChars(clazz.name)
} else {
fingerprintForObject(type, type, alreadySeen, hasher, factory)
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
fingerprintForObject(type, type, alreadySeen, hasher, factory)
}
}
// ... and concatentate the type data for each parameter type.
type.actualTypeArguments.fold(startingHash) { orig, paramType -> fingerprintForType(paramType, type, alreadySeen, orig, factory) }
@ -386,11 +395,16 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
} else if (type is TypeVariable<*>) {
// TODO: include bounds
hasher.putUnencodedChars(type.name).putUnencodedChars(TYPE_VARIABLE_HASH)
} else {
} else if (type is WildcardType) {
hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
}
else {
throw NotSerializableException("Don't know how to hash")
}
} catch(e: NotSerializableException) {
throw NotSerializableException("${e.message} -> $type")
val msg = "${e.message} -> $type"
logger.error(msg, e)
throw NotSerializableException(msg)
}
}
}
@ -403,6 +417,6 @@ private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: Mu
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).fold(hasher.putUnencodedChars(name)) { orig, prop ->
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory).putUnencodedChars(prop.name).putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
return hasher
}

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.reflect.TypeToken
import org.apache.qpid.proton.codec.Data
import java.beans.IndexedPropertyDescriptor
import java.beans.Introspector
import java.io.NotSerializableException
import java.lang.reflect.*
@ -64,7 +65,7 @@ internal fun <T : Any> propertiesForSerialization(kotlinConstructor: KFunction<T
return if (kotlinConstructor != null) propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) else propertiesForSerializationFromAbstract(clazz, type, factory)
}
private fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructor: KFunction<T>, type: Type, factory: SerializerFactory): Collection<PropertySerializer> {
val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType
@ -73,13 +74,14 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
val rc: MutableList<PropertySerializer> = ArrayList(kotlinConstructor.parameters.size)
for (param in kotlinConstructor.parameters) {
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
val matchingProperty = properties[name] ?: throw NotSerializableException("No property matching constructor parameter named $name of $clazz." +
val matchingProperty = properties[name] ?:
throw NotSerializableException("No property matching constructor parameter named $name of $clazz." +
" If using Java, check that you have the -parameters option specified in the Java compiler.")
// Check that the method has a getter in java.
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz." +
" If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler.")
val returnType = resolveTypeVariables(getter.genericReturnType, type)
if (constructorParamTakesReturnTypeOfGetter(getter, param)) {
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
rc += PropertySerializer.make(name, getter, returnType, factory)
} else {
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
@ -88,11 +90,14 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
return rc
}
private fun constructorParamTakesReturnTypeOfGetter(getter: Method, param: KParameter): Boolean = TypeToken.of(param.type.javaType).isSupertypeOf(getter.genericReturnType)
private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean {
val typeToken = TypeToken.of(param.type.javaType)
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
}
private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): Collection<PropertySerializer> {
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.sortedBy { it.name }
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.sortedBy { it.name }.filterNot { it is IndexedPropertyDescriptor }
val rc: MutableList<PropertySerializer> = ArrayList(properties.size)
for (property in properties) {
// Check that the method has a getter in java.
@ -103,23 +108,26 @@ private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type,
return rc
}
internal fun interfacesForSerialization(type: Type): List<Type> {
internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> {
val interfaces = LinkedHashSet<Type>()
exploreType(type, interfaces)
exploreType(type, interfaces, serializerFactory)
return interfaces.toList()
}
private fun exploreType(type: Type?, interfaces: MutableSet<Type>) {
private fun exploreType(type: Type?, interfaces: MutableSet<Type>, serializerFactory: SerializerFactory) {
val clazz = type?.asClass()
if (clazz != null) {
if (clazz.isInterface) interfaces += type
if (clazz.isInterface) {
if(serializerFactory.isNotWhitelisted(clazz)) return // We stop exploring once we reach a branch that has no `CordaSerializable` annotation or whitelisting.
else interfaces += type
}
for (newInterface in clazz.genericInterfaces) {
if (newInterface !in interfaces) {
exploreType(resolveTypeVariables(newInterface, type), interfaces)
exploreType(resolveTypeVariables(newInterface, type), interfaces, serializerFactory)
}
}
val superClass = clazz.genericSuperclass ?: return
exploreType(resolveTypeVariables(superClass, type), interfaces)
exploreType(resolveTypeVariables(superClass, type), interfaces, serializerFactory)
}
}
@ -159,21 +167,20 @@ private fun resolveTypeVariables(actualType: Type, contextType: Type?): Type {
}
internal fun Type.asClass(): Class<*>? {
return if (this is Class<*>) {
this
} else if (this is ParameterizedType) {
this.rawType.asClass()
} else if (this is GenericArrayType) {
this.genericComponentType.asClass()?.arrayClass()
} else null
return when {
this is Class<*> -> this
this is ParameterizedType -> this.rawType.asClass()
this is GenericArrayType -> this.genericComponentType.asClass()?.arrayClass()
else -> null
}
}
internal fun Type.asArray(): Type? {
return if (this is Class<*>) {
this.arrayClass()
} else if (this is ParameterizedType) {
DeserializedGenericArrayType(this)
} else null
return when {
this is Class<*> -> this.arrayClass()
this is ParameterizedType -> DeserializedGenericArrayType(this)
else -> null
}
}
internal fun Class<*>.arrayClass(): Class<*> = java.lang.reflect.Array.newInstance(this, 0).javaClass

View File

@ -14,7 +14,7 @@ import kotlin.collections.LinkedHashSet
* @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple
* instances and threads.
*/
open class SerializationOutput(internal val serializerFactory: SerializerFactory = SerializerFactory()) {
open class SerializationOutput(internal val serializerFactory: SerializerFactory) {
// TODO: we're not supporting object refs yet
private val objectHistory: MutableMap<Any, Int> = IdentityHashMap()
private val serializerHistory: MutableSet<AMQPSerializer<*>> = LinkedHashSet()

View File

@ -4,11 +4,7 @@ import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeResolver
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.carpenter.CarpenterSchemas
import net.corda.nodeapi.internal.serialization.carpenter.ClassCarpenter
import net.corda.nodeapi.internal.serialization.carpenter.MetaCarpenter
import net.corda.nodeapi.internal.serialization.carpenter.carpenterSchema
import net.corda.nodeapi.internal.serialization.carpenter.*
import org.apache.qpid.proton.amqp.*
import java.io.NotSerializableException
import java.lang.reflect.GenericArrayType
@ -20,6 +16,8 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import javax.annotation.concurrent.ThreadSafe
data class schemaAndDescriptor (val schema: Schema, val typeDescriptor: Any)
/**
* Factory of serializers designed to be shared across threads and invocations.
*/
@ -32,9 +30,6 @@ import javax.annotation.concurrent.ThreadSafe
// TODO: profile for performance in general
// TODO: use guava caches etc so not unbounded
// TODO: do we need to support a transient annotation to exclude certain properties?
// TODO: incorporate the class carpenter for classes not on the classpath.
// TODO: apply class loader logic and an "app context" throughout this code.
// TODO: schema evolution solution when the fingerprints do not line up.
// TODO: allow definition of well known types that are left out of the schema.
// TODO: generally map Object to '*' all over the place in the schema and make sure use of '*' amd '?' is consistent and documented in generics.
// TODO: found a document that states textual descriptors are Symbols. Adjust schema class appropriately.
@ -44,11 +39,19 @@ import javax.annotation.concurrent.ThreadSafe
// TODO: need to rethink matching of constructor to properties in relation to implementing interfaces and needing those properties etc.
// TODO: need to support super classes as well as interfaces with our current code base... what's involved? If we continue to ban, what is the impact?
@ThreadSafe
class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) {
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
private val classCarpenter = ClassCarpenter()
private val classCarpenter = ClassCarpenter(cl)
val classloader : ClassLoader
get() = classCarpenter.classloader
fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: ObjectSerializer) : AMQPSerializer<Any> {
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
EvolutionSerializer.make(typeNotation as CompositeType, newSerializer, this)
}
}
/**
* Look up, and manufacture if necessary, a serializer for the given type.
@ -157,8 +160,9 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
@Throws(NotSerializableException::class)
fun get(typeDescriptor: Any, schema: Schema): AMQPSerializer<Any> {
return serializersByDescriptor[typeDescriptor] ?: {
processSchema(schema)
serializersByDescriptor[typeDescriptor] ?: throw NotSerializableException("Could not find type matching descriptor $typeDescriptor.")
processSchema(schemaAndDescriptor(schema, typeDescriptor))
serializersByDescriptor[typeDescriptor] ?: throw NotSerializableException(
"Could not find type matching descriptor $typeDescriptor.")
}()
}
@ -176,15 +180,25 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
}
}
private fun processSchema(schema: Schema, sentinal: Boolean = false) {
/**
* Iterate over an AMQP schema, for each type ascertain weather it's on ClassPath of [classloader] amd
* if not use the [ClassCarpenter] to generate a class to use in it's place
*/
private fun processSchema(schema: schemaAndDescriptor, sentinel: Boolean = false) {
val carpenterSchemas = CarpenterSchemas.newInstance()
for (typeNotation in schema.types) {
for (typeNotation in schema.schema.types) {
try {
processSchemaEntry(typeNotation, classCarpenter.classloader)
val serialiser = processSchemaEntry(typeNotation)
// if we just successfully built a serialiser for the type but the type fingerprint
// doesn't match that of the serialised object then we are dealing with different
// instance of the class, as such we need to build an EvolutionSerialiser
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
getEvolutionSerializer(typeNotation, serialiser as ObjectSerializer)
}
} catch (e: ClassNotFoundException) {
if (sentinal || (typeNotation !is CompositeType)) throw e
typeNotation.carpenterSchema(
classLoaders = listOf(classCarpenter.classloader), carpenterSchemas = carpenterSchemas)
if (sentinel || (typeNotation !is CompositeType)) throw e
typeNotation.carpenterSchema(classloader, carpenterSchemas = carpenterSchemas)
}
}
@ -195,25 +209,21 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
}
}
private fun processSchemaEntry(typeNotation: TypeNotation,
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
when (typeNotation) {
is CompositeType -> processCompositeType(typeNotation, cl) // java.lang.Class (whether a class or interface)
private fun processSchemaEntry(typeNotation: TypeNotation) = when (typeNotation) {
is CompositeType -> processCompositeType(typeNotation) // java.lang.Class (whether a class or interface)
is RestrictedType -> processRestrictedType(typeNotation) // Collection / Map, possibly with generics
}
private fun processRestrictedType(typeNotation: RestrictedType): AMQPSerializer<Any> {
// TODO: class loader logic, and compare the schema.
val type = typeForName(typeNotation.name, classloader)
return get(null, type)
}
private fun processRestrictedType(typeNotation: RestrictedType) {
private fun processCompositeType(typeNotation: CompositeType): AMQPSerializer<Any> {
// TODO: class loader logic, and compare the schema.
val type = typeForName(typeNotation.name)
get(null, type)
}
private fun processCompositeType(typeNotation: CompositeType,
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
// TODO: class loader logic, and compare the schema.
val type = typeForName(typeNotation.name, cl)
get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type)
val type = typeForName(typeNotation.name, classloader)
return get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type)
}
private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer<Any> = serializersByType.computeIfAbsent(type) {
@ -222,7 +232,8 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
} else {
findCustomSerializer(clazz, declaredType) ?: run {
if (type.isArray()) {
whitelisted(type.componentType())
// Allow Object[] since this can be quite common (i.e. an untyped array)
if(type.componentType() != Object::class.java) whitelisted(type.componentType())
if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this)
else ArraySerializer.make(type, this)
} else if (clazz.kotlin.objectInstance != null) {
@ -256,11 +267,15 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
private fun whitelisted(type: Type) {
val clazz = type.asClass()!!
if (!whitelist.hasListed(clazz) && !hasAnnotationInHierarchy(clazz)) {
if (isNotWhitelisted(clazz)) {
throw NotSerializableException("Class $type is not on the whitelist or annotated with @CordaSerializable.")
}
}
// Ignore SimpleFieldAccess as we add it to anything we build in the carpenter.
internal fun isNotWhitelisted(clazz: Class<*>): Boolean = clazz == SimpleFieldAccess::class.java ||
(!whitelist.hasListed(clazz) && !hasAnnotationInHierarchy(clazz))
// Recursively check the class, interfaces and superclasses for our annotation.
internal fun hasAnnotationInHierarchy(type: Class<*>): Boolean {
return type.isAnnotationPresent(CordaSerializable::class.java) ||
@ -270,7 +285,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
private fun makeMapSerializer(declaredType: ParameterizedType): AMQPSerializer<Any> {
val rawType = declaredType.rawType as Class<*>
rawType.checkNotUnorderedHashMap()
rawType.checkNotUnsupportedHashMap()
return MapSerializer(declaredType, this)
}
@ -319,14 +334,13 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
}
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
is WildcardType -> "Any"
else -> throw NotSerializableException("Unable to render type $type to a string.")
}
private fun typeForName(
name: String,
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader): Type {
private fun typeForName(name: String, classloader: ClassLoader): Type {
return if (name.endsWith("[]")) {
val elementType = typeForName(name.substring(0, name.lastIndex - 1))
val elementType = typeForName(name.substring(0, name.lastIndex - 1), classloader)
if (elementType is ParameterizedType || elementType is GenericArrayType) {
DeserializedGenericArrayType(elementType)
} else if (elementType is Class<*>) {
@ -349,7 +363,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
else -> throw NotSerializableException("Not able to deserialize array type: $name")
}
} else {
DeserializedParameterizedType.make(name, cl)
DeserializedParameterizedType.make(name, classloader)
}
}
}

View File

@ -10,7 +10,7 @@ import java.lang.reflect.Type
*/
class SingletonSerializer(override val type: Class<*>, val singleton: Any, factory: SerializerFactory) : AMQPSerializer<Any> {
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
private val interfaces = interfacesForSerialization(type)
private val interfaces = interfacesForSerialization(type, factory)
private fun generateProvides(): List<String> = interfaces.map { it.typeName }

View File

@ -0,0 +1,15 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
/**
* A serializer for [Class] that uses [ClassProxy] proxy object to write out
*/
class ClassSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Class<*>, ClassSerializer.ClassProxy>(Class::class.java, ClassProxy::class.java, factory) {
override fun toProxy(obj: Class<*>): ClassProxy = ClassProxy(obj.name)
override fun fromProxy(proxy: ClassProxy): Class<*> = Class.forName(proxy.className, true, factory.classloader)
data class ClassProxy(val className: String)
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.Duration
/**
* A serializer for [Duration] that uses a proxy object to write out the seconds and the nanos.
*/
class DurationSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Duration, DurationSerializer.DurationProxy>(Duration::class.java, DurationProxy::class.java, factory) {
override fun toProxy(obj: Duration): DurationProxy = DurationProxy(obj.seconds, obj.nano)
override fun fromProxy(proxy: DurationProxy): Duration = Duration.ofSeconds(proxy.seconds, proxy.nanos.toLong())
data class DurationProxy(val seconds: Long, val nanos: Int)
}

View File

@ -8,8 +8,6 @@ import java.time.Instant
* A serializer for [Instant] that uses a proxy object to write out the seconds since the epoch and the nanos.
*/
class InstantSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Instant, InstantSerializer.InstantProxy>(Instant::class.java, InstantProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
override fun toProxy(obj: Instant): InstantProxy = InstantProxy(obj.epochSecond, obj.nano)
override fun fromProxy(proxy: InstantProxy): Instant = Instant.ofEpochSecond(proxy.epochSeconds, proxy.nanos.toLong())

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.LocalDate
/**
* A serializer for [LocalDate] that uses a proxy object to write out the year, month and day.
*/
class LocalDateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<LocalDate, LocalDateSerializer.LocalDateProxy>(LocalDate::class.java, LocalDateProxy::class.java, factory) {
override fun toProxy(obj: LocalDate): LocalDateProxy = LocalDateProxy(obj.year, obj.monthValue.toByte(), obj.dayOfMonth.toByte())
override fun fromProxy(proxy: LocalDateProxy): LocalDate = LocalDate.of(proxy.year, proxy.month.toInt(), proxy.day.toInt())
data class LocalDateProxy(val year: Int, val month: Byte, val day: Byte)
}

View File

@ -0,0 +1,20 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
/**
* A serializer for [LocalDateTime] that uses a proxy object to write out the date and time.
*/
class LocalDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<LocalDateTime, LocalDateTimeSerializer.LocalDateTimeProxy>(LocalDateTime::class.java, LocalDateTimeProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateSerializer(factory), LocalTimeSerializer(factory))
override fun toProxy(obj: LocalDateTime): LocalDateTimeProxy = LocalDateTimeProxy(obj.toLocalDate(), obj.toLocalTime())
override fun fromProxy(proxy: LocalDateTimeProxy): LocalDateTime = LocalDateTime.of(proxy.date, proxy.time)
data class LocalDateTimeProxy(val date: LocalDate, val time: LocalTime)
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.LocalTime
/**
* A serializer for [LocalTime] that uses a proxy object to write out the hours, minutes, seconds and the nanos.
*/
class LocalTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<LocalTime, LocalTimeSerializer.LocalTimeProxy>(LocalTime::class.java, LocalTimeProxy::class.java, factory) {
override fun toProxy(obj: LocalTime): LocalTimeProxy = LocalTimeProxy(obj.hour.toByte(), obj.minute.toByte(), obj.second.toByte(), obj.nano)
override fun fromProxy(proxy: LocalTimeProxy): LocalTime = LocalTime.of(proxy.hour.toInt(), proxy.minute.toInt(), proxy.second.toInt(), proxy.nano)
data class LocalTimeProxy(val hour: Byte, val minute: Byte, val second: Byte, val nano: Int)
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [MonthDay] that uses a proxy object to write out the integer form.
*/
class MonthDaySerializer(factory: SerializerFactory) : CustomSerializer.Proxy<MonthDay, MonthDaySerializer.MonthDayProxy>(MonthDay::class.java, MonthDayProxy::class.java, factory) {
override fun toProxy(obj: MonthDay): MonthDayProxy = MonthDayProxy(obj.monthValue.toByte(), obj.dayOfMonth.toByte())
override fun fromProxy(proxy: MonthDayProxy): MonthDay = MonthDay.of(proxy.month.toInt(), proxy.day.toInt())
data class MonthDayProxy(val month: Byte, val day: Byte)
}

View File

@ -0,0 +1,18 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [OffsetDateTime] that uses a proxy object to write out the date and zone offset.
*/
class OffsetDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<OffsetDateTime, OffsetDateTimeSerializer.OffsetDateTimeProxy>(OffsetDateTime::class.java, OffsetDateTimeProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))
override fun toProxy(obj: OffsetDateTime): OffsetDateTimeProxy = OffsetDateTimeProxy(obj.toLocalDateTime(), obj.offset)
override fun fromProxy(proxy: OffsetDateTimeProxy): OffsetDateTime = OffsetDateTime.of(proxy.dateTime, proxy.offset)
data class OffsetDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset)
}

View File

@ -0,0 +1,18 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [OffsetTime] that uses a proxy object to write out the time and zone offset.
*/
class OffsetTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<OffsetTime, OffsetTimeSerializer.OffsetTimeProxy>(OffsetTime::class.java, OffsetTimeProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalTimeSerializer(factory), ZoneIdSerializer(factory))
override fun toProxy(obj: OffsetTime): OffsetTimeProxy = OffsetTimeProxy(obj.toLocalTime(), obj.offset)
override fun fromProxy(proxy: OffsetTimeProxy): OffsetTime = OffsetTime.of(proxy.time, proxy.offset)
data class OffsetTimeProxy(val time: LocalTime, val offset: ZoneOffset)
}

View File

@ -0,0 +1,20 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.OpaqueBytesSubSequence
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
/**
* A serializer for [OpaqueBytesSubSequence] that uses a proxy object to write out only content included into sequence
* to save on network bandwidth
* Uses [OpaqueBytes] as a proxy
*/
class OpaqueBytesSubSequenceSerializer(factory: SerializerFactory) :
CustomSerializer.Proxy<OpaqueBytesSubSequence, OpaqueBytes>(OpaqueBytesSubSequence::class.java, OpaqueBytes::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
override fun toProxy(obj: OpaqueBytesSubSequence): OpaqueBytes = obj.copy() as OpaqueBytes
override fun fromProxy(proxy: OpaqueBytes): OpaqueBytesSubSequence = OpaqueBytesSubSequence(proxy.bytes, proxy.offset, proxy.size)
}

View File

@ -0,0 +1,30 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.core.identity.PartyAndCertificate
import net.corda.nodeapi.internal.serialization.amqp.*
import java.io.ByteArrayInputStream
import java.io.NotSerializableException
import java.security.cert.CertPath
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
/**
* A serializer that writes out a party and certificate in encoded format.
*/
class PartyAndCertificateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<PartyAndCertificate, PartyAndCertificateSerializer.PartyAndCertificateProxy>(PartyAndCertificate::class.java, PartyAndCertificateProxy::class.java, factory) {
override fun toProxy(obj: PartyAndCertificate): PartyAndCertificateProxy = PartyAndCertificateProxy(obj.certPath.type, obj.certPath.encoded)
override fun fromProxy(proxy: PartyAndCertificateProxy): PartyAndCertificate {
try {
val cf = CertificateFactory.getInstance(proxy.type)
return PartyAndCertificate(cf.generateCertPath(ByteArrayInputStream(proxy.encoded)))
} catch (ce: CertificateException) {
val nse = NotSerializableException("java.security.cert.CertPath: " + type)
nse.initCause(ce)
throw nse
}
}
data class PartyAndCertificateProxy(val type: String, val encoded: ByteArray)
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [Period] that uses a proxy object to write out the integer form.
*/
class PeriodSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Period, PeriodSerializer.PeriodProxy>(Period::class.java, PeriodProxy::class.java, factory) {
override fun toProxy(obj: Period): PeriodProxy = PeriodProxy(obj.years, obj.months, obj.days)
override fun fromProxy(proxy: PeriodProxy): Period = Period.of(proxy.years, proxy.months, proxy.days)
data class PeriodProxy(val years: Int, val months: Int, val days: Int)
}

View File

@ -10,8 +10,6 @@ import java.security.PublicKey
* A serializer that writes out a public key in X.509 format.
*/
object PublicKeySerializer : CustomSerializer.Implements<PublicKey>(PublicKey::class.java) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: PublicKey, data: Data, type: Type, output: SerializationOutput) {

View File

@ -19,7 +19,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
val constructor = constructorForDeserialization(obj.javaClass)
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
for (prop in props) {
extraProperties[prop.name] = prop.readMethod.invoke(obj)
extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
}
} catch(e: NotSerializableException) {
}
@ -71,8 +71,6 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
}
class StackTraceElementSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<StackTraceElement, StackTraceElementSerializer.StackTraceElementProxy>(StackTraceElement::class.java, StackTraceElementProxy::class.java, factory) {
override val additionalSerializers: Iterable<CustomSerializer<Any>> = emptyList()
override fun toProxy(obj: StackTraceElement): StackTraceElementProxy = StackTraceElementProxy(obj.className, obj.methodName, obj.fileName, obj.lineNumber)
override fun fromProxy(proxy: StackTraceElementProxy): StackTraceElement = StackTraceElement(proxy.declaringClass, proxy.methodName, proxy.fileName, proxy.lineNumber)

View File

@ -10,8 +10,6 @@ import java.lang.reflect.Type
* Custom serializer for X500 names that utilizes their ASN.1 encoding on the wire.
*/
object X500NameSerializer : CustomSerializer.Implements<X500Name>(X500Name::class.java) {
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: X500Name, data: Data, type: Type, output: SerializationOutput) {

View File

@ -0,0 +1,23 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import org.bouncycastle.cert.X509CertificateHolder
import java.lang.reflect.Type
/**
* A serializer that writes out a certificate in X.509 format.
*/
object X509CertificateHolderSerializer : CustomSerializer.Implements<X509CertificateHolder>(X509CertificateHolder::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: X509CertificateHolder, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509CertificateHolder {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return X509CertificateHolder(bits)
}
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [YearMonth] that uses a proxy object to write out the integer form.
*/
class YearMonthSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<YearMonth, YearMonthSerializer.YearMonthProxy>(YearMonth::class.java, YearMonthProxy::class.java, factory) {
override fun toProxy(obj: YearMonth): YearMonthProxy = YearMonthProxy(obj.year, obj.monthValue.toByte())
override fun fromProxy(proxy: YearMonthProxy): YearMonth = YearMonth.of(proxy.year, proxy.month.toInt())
data class YearMonthProxy(val year: Int, val month: Byte)
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [Year] that uses a proxy object to write out the integer form.
*/
class YearSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Year, YearSerializer.YearProxy>(Year::class.java, YearProxy::class.java, factory) {
override fun toProxy(obj: Year): YearProxy = YearProxy(obj.value)
override fun fromProxy(proxy: YearProxy): Year = Year.of(proxy.year)
data class YearProxy(val year: Int)
}

View File

@ -0,0 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [ZoneId] that uses a proxy object to write out the string form.
*/
class ZoneIdSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<ZoneId, ZoneIdSerializer.ZoneIdProxy>(ZoneId::class.java, ZoneIdProxy::class.java, factory) {
override fun toProxy(obj: ZoneId): ZoneIdProxy = ZoneIdProxy(obj.id)
override fun fromProxy(proxy: ZoneIdProxy): ZoneId = ZoneId.of(proxy.id)
data class ZoneIdProxy(val id: String)
}

View File

@ -0,0 +1,26 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.time.*
/**
* A serializer for [ZonedDateTime] that uses a proxy object to write out the date, time, offset and zone.
*/
class ZonedDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<ZonedDateTime, ZonedDateTimeSerializer.ZonedDateTimeProxy>(ZonedDateTime::class.java, ZonedDateTimeProxy::class.java, factory) {
// Java deserialization of `ZonedDateTime` uses a private method. We will resolve this somewhat statically
// so that any change to internals of `ZonedDateTime` is detected early.
companion object {
val ofLenient = ZonedDateTime::class.java.getDeclaredMethod("ofLenient", LocalDateTime::class.java, ZoneOffset::class.java, ZoneId::class.java)
init {
ofLenient.isAccessible = true
}
}
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))
override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone)
override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ofLenient.invoke(null, proxy.dateTime, proxy.offset, proxy.zone) as ZonedDateTime
data class ZonedDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset, val zone: ZoneId)
}

View File

@ -4,13 +4,11 @@ import net.corda.nodeapi.internal.serialization.amqp.CompositeType
import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField
import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema
fun AMQPSchema.carpenterSchema(
loaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()))
: CarpenterSchemas {
fun AMQPSchema.carpenterSchema(classloader: ClassLoader) : CarpenterSchemas {
val rtn = CarpenterSchemas.newInstance()
types.filterIsInstance<CompositeType>().forEach {
it.carpenterSchema(classLoaders = loaders, carpenterSchemas = rtn)
it.carpenterSchema(classloader, carpenterSchemas = rtn)
}
return rtn
@ -19,10 +17,9 @@ fun AMQPSchema.carpenterSchema(
/**
* if we can load the class then we MUST know about all of it's composite elements
*/
private fun CompositeType.validatePropertyTypes(
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader())) {
private fun CompositeType.validatePropertyTypes(classloader: ClassLoader) {
fields.forEach {
if (!it.validateType(classLoaders)) throw UncarpentableException(name, it.name, it.type)
if (!it.validateType(classloader)) throw UncarpentableException(name, it.name, it.type)
}
}
@ -34,24 +31,21 @@ fun AMQPField.typeAsString() = if (type == "*") requires[0] else type
* b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated
* at this time
*
* @param classLoaders list of classLoaders, defaulting toe the system class loader, that might
* be used to load objects
* @param classloader the class loader provided dby the [SerializationContext]
* @param carpenterSchemas structure that holds the dependency tree and list of classes that
* need constructing
* @param force by default a schema is not added to [carpenterSchemas] if it already exists
* on the class path. For testing purposes schema generation can be forced
*/
fun CompositeType.carpenterSchema(
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()),
carpenterSchemas: CarpenterSchemas,
force: Boolean = false) {
if (classLoaders.exists(name)) {
validatePropertyTypes(classLoaders)
fun CompositeType.carpenterSchema(classloader: ClassLoader,
carpenterSchemas: CarpenterSchemas,
force: Boolean = false) {
if (classloader.exists(name)) {
validatePropertyTypes(classloader)
if (!force) return
}
val providesList = mutableListOf<Class<*>>()
var isInterface = false
var isCreatable = true
@ -62,7 +56,7 @@ fun CompositeType.carpenterSchema(
}
try {
providesList.add(classLoaders.loadIfExists(it))
providesList.add(classloader.loadClass(it))
} catch (e: ClassNotFoundException) {
carpenterSchemas.addDepPair(this, name, it)
isCreatable = false
@ -73,7 +67,7 @@ fun CompositeType.carpenterSchema(
fields.forEach {
try {
m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders))
m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classloader))
} catch (e: ClassNotFoundException) {
carpenterSchemas.addDepPair(this, name, it.typeAsString())
isCreatable = false
@ -110,33 +104,18 @@ val typeStrToType: Map<Pair<String, Boolean>, Class<out Any?>> = mapOf(
Pair("byte", false) to Byte::class.javaObjectType
)
fun AMQPField.getTypeAsClass(
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader())
) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
"string" -> String::class.java
"*" -> classLoaders.loadIfExists(requires[0])
else -> classLoaders.loadIfExists(type)
"*" -> classloader.loadClass(requires[0])
else -> classloader.loadClass(type)
}
fun AMQPField.validateType(
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader())
) = when (type) {
fun AMQPField.validateType(classloader: ClassLoader) = when (type) {
"byte", "int", "string", "short", "long", "char", "boolean", "double", "float" -> true
"*" -> classLoaders.exists(requires[0])
else -> classLoaders.exists(type)
"*" -> classloader.exists(requires[0])
else -> classloader.exists(type)
}
private fun List<ClassLoader>.exists(clazz: String) = this.find {
try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false }
} != null
private fun ClassLoader.exists(clazz: String) = run {
try { this.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } }
private fun List<ClassLoader>.loadIfExists(clazz: String): Class<*> {
this.forEach {
try {
return it.loadClass(clazz)
} catch (e: ClassNotFoundException) {
return@forEach
}
}
throw ClassNotFoundException(clazz)
}

View File

@ -1,8 +1,10 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.serialization.CordaSerializable
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import java.lang.Character.isJavaIdentifierPart
import java.lang.Character.isJavaIdentifierStart
@ -18,7 +20,8 @@ interface SimpleFieldAccess {
operator fun get(name: String): Any?
}
class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) {
class CarpenterClassLoader (parentClassLoader: ClassLoader = Thread.currentThread().contextClassLoader) :
ClassLoader(parentClassLoader) {
fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size)
}
@ -66,14 +69,14 @@ class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoad
*
* Equals/hashCode methods are not yet supported.
*/
class ClassCarpenter {
class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader) {
// TODO: Generics.
// TODO: Sandbox the generated code when a security manager is in use.
// TODO: Generate equals/hashCode.
// TODO: Support annotations.
// TODO: isFoo getter patterns for booleans (this is what Kotlin generates)
val classloader = CarpenterClassLoader()
val classloader = CarpenterClassLoader(cl)
private val _loaded = HashMap<String, Class<*>>()
private val String.jvm: String get() = replace(".", "/")
@ -118,6 +121,7 @@ class ClassCarpenter {
with(cw) {
visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, schema.jvmName, null, "java/lang/Object", interfaces)
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
generateAbstractGetters(schema)
@ -135,6 +139,7 @@ class ClassCarpenter {
with(cw) {
visit(V1_8, ACC_PUBLIC + ACC_SUPER, schema.jvmName, null, superName, interfaces.toTypedArray())
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
generateFields(schema)
generateConstructor(schema)

View File

@ -73,9 +73,7 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas, val cc : Class
// we're out of blockers so we can now create the type
if (schemas.dependencies[dependent]?.second?.isEmpty() ?: false) {
(schemas.dependencies.remove (dependent)?.first as CompositeType).carpenterSchema (
classLoaders = listOf<ClassLoader> (
ClassLoader.getSystemClassLoader(),
cc.classloader),
classloader = cc.classloader,
carpenterSchemas = schemas)
}
}

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import net.corda.core.serialization.SerializedBytes;
import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
@ -167,8 +168,9 @@ public class JavaSerializationOutputTests {
}
private Object serdes(Object obj) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory();
SerializationOutput ser = new SerializationOutput(factory);
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(obj);
DecoderImpl decoder = new DecoderImpl();
@ -186,13 +188,13 @@ public class JavaSerializationOutputTests {
Envelope result = (Envelope) decoder.readObject();
assertTrue(result != null);
DeserializationInput des = new DeserializationInput();
DeserializationInput des = new DeserializationInput(factory2);
Object desObj = des.deserialize(bytes, Object.class);
assertTrue(Objects.deepEquals(obj, desObj));
// Now repeat with a re-used factory
SerializationOutput ser2 = new SerializationOutput(factory);
DeserializationInput des2 = new DeserializationInput(factory);
SerializationOutput ser2 = new SerializationOutput(factory1);
DeserializationInput des2 = new DeserializationInput(factory1);
Object desObj2 = des2.deserialize(ser2.serialize(obj), Object.class);
assertTrue(Objects.deepEquals(obj, desObj2));
// TODO: check schema is as expected

View File

@ -6,12 +6,17 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
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.SerializationDefaults.P2P_CONTEXT
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.serialization.AMQP_ENABLED
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.WireTransactionSerializer
import net.corda.nodeapi.internal.serialization.withTokenContext
@ -66,9 +71,6 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
@ -99,7 +101,7 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as Contract
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.legalContractReference)
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.declaredField<Any?>("legalContractReference").value)
}
fun fakeAttachment(filepath: String, content: String): ByteArray {
@ -190,7 +192,7 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl)
val contract = contractClass.newInstance() as Contract
assertEquals(cl, contract.javaClass.classLoader)
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.legalContractReference)
assertEquals(SecureHash.sha256("https://anotherdummy.org"), contract.declaredField<Any?>("legalContractReference").value)
}
@ -266,6 +268,32 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
assertNotNull(state3)
}
@Test
fun `test serialization of SecureHash`() {
val secureHash = SecureHash.randomSHA256()
val bytes = secureHash.serialize()
val copiedSecuredHash = bytes.deserialize()
assertEquals(secureHash, copiedSecuredHash)
}
@Test
fun `test serialization of OpaqueBytes`() {
val opaqueBytes = OpaqueBytes("0123456789".toByteArray())
val bytes = opaqueBytes.serialize()
val copiedOpaqueBytes = bytes.deserialize()
assertEquals(opaqueBytes, copiedOpaqueBytes)
}
@Test
fun `test serialization of sub-sequence OpaqueBytes`() {
val bytesSequence = ByteSequence.of("0123456789".toByteArray(), 3 ,2)
val bytes = bytesSequence.serialize()
val copiedBytesSequence = bytes.deserialize()
assertEquals(bytesSequence, copiedBytesSequence)
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
@ -304,7 +332,8 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
}
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
// Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not
fun `test deserialize of WireTransaction where contract cannot be found`() = kryoSpecific {
val child = ClassLoaderForTests()
val contractClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
@ -324,8 +353,19 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() {
// use empty attachmentStorage
val e = assertFailsWith(MissingAttachmentsException::class) {
bytes.deserialize(context = P2P_CONTEXT.withAttachmentStorage(MockAttachmentStorage()))
val mockAttStorage = MockAttachmentStorage()
bytes.deserialize(context = P2P_CONTEXT.withAttachmentStorage(mockAttStorage))
if(mockAttStorage.openAttachment(attachmentRef) == null) {
throw MissingAttachmentsException(listOf(attachmentRef))
}
}
assertEquals(attachmentRef, e.ids.single())
}
}
private fun kryoSpecific(function: () -> Unit) = if(!AMQP_ENABLED) {
function()
} else {
loggerFor<AttachmentClassLoaderTests>().info("Ignoring Kryo specific test")
}
}

View File

@ -87,73 +87,73 @@ class CordaClassResolverTests {
}
val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P)
val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P)
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P)
private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P)
@Test
fun `Annotation on enum works for specialised entries`() {
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
@Suppress("UNSUPPORTED_FEATURE")
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(Foo.Bar::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java)
}
@Test
fun `Annotation on array element works`() {
val values = arrayOf(Element())
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(values.javaClass)
CordaClassResolver(emptyWhitelistContext).getRegistration(values.javaClass)
}
@Test
fun `Annotation not needed on abstract class`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(AbstractClass::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(AbstractClass::class.java)
}
@Test
fun `Annotation not needed on interface`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(Interface::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(Interface::class.java)
}
@Test
fun `Calling register method on modified Kryo does not consult the whitelist`() {
val kryo = CordaKryo(CordaClassResolver(factory, emptyWhitelistContext))
val kryo = CordaKryo(CordaClassResolver(emptyWhitelistContext))
kryo.register(NotSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Calling register method on unmodified Kryo does consult the whitelist`() {
val kryo = Kryo(CordaClassResolver(factory, emptyWhitelistContext), MapReferenceResolver())
val kryo = Kryo(CordaClassResolver(emptyWhitelistContext), MapReferenceResolver())
kryo.register(NotSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Annotation is needed without whitelisting`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(NotSerializable::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(NotSerializable::class.java)
}
@Test
fun `Annotation is not needed with whitelisting`() {
val resolver = CordaClassResolver(factory, emptyWhitelistContext.withWhitelisted(NotSerializable::class.java))
val resolver = CordaClassResolver(emptyWhitelistContext.withWhitelisted(NotSerializable::class.java))
resolver.getRegistration(NotSerializable::class.java)
}
@Test
fun `Annotation not needed on Object`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(Object::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(Object::class.java)
}
@Test
fun `Annotation not needed on primitive`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(Integer.TYPE)
CordaClassResolver(emptyWhitelistContext).getRegistration(Integer.TYPE)
}
@Test(expected = KryoException::class)
fun `Annotation does not work for custom serializable`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(CustomSerializable::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(CustomSerializable::class.java)
}
@Test(expected = KryoException::class)
fun `Annotation does not work in conjunction with Kryo annotation`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(DefaultSerializable::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(DefaultSerializable::class.java)
}
private fun importJar(storage: AttachmentStorage) = AttachmentClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) }
@ -164,23 +164,23 @@ class CordaClassResolverTests {
val attachmentHash = importJar(storage)
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! })
val attachedClass = Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, classLoader)
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(attachedClass)
CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass)
}
@Test
fun `Annotation is inherited from interfaces`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(SerializableViaInterface::class.java)
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(SerializableViaSubInterface::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaInterface::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaSubInterface::class.java)
}
@Test
fun `Annotation is inherited from superclass`() {
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(SubElement::class.java)
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(SubSubElement::class.java)
CordaClassResolver(factory, emptyWhitelistContext).getRegistration(SerializableViaSuperSubInterface::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SubElement::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SubSubElement::class.java)
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaSuperSubInterface::class.java)
}
// Blacklist tests.
// Blacklist tests. Note: leave the variable public or else expected messages do not work correctly
@get:Rule
val expectedEx = ExpectedException.none()!!
@ -188,7 +188,7 @@ class CordaClassResolverTests {
fun `Check blacklisted class`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("Class java.util.HashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// HashSet is blacklisted.
resolver.getRegistration(HashSet::class.java)
}
@ -198,7 +198,7 @@ class CordaClassResolverTests {
fun `Check blacklisted subclass`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.nodeapi.internal.serialization.CordaClassResolverTests\$SubHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// SubHashSet extends the blacklisted HashSet.
resolver.getRegistration(SubHashSet::class.java)
}
@ -208,35 +208,35 @@ class CordaClassResolverTests {
fun `Check blacklisted subsubclass`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.nodeapi.internal.serialization.CordaClassResolverTests\$SubSubHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// SubSubHashSet extends SubHashSet, which extends the blacklisted HashSet.
resolver.getRegistration(SubSubHashSet::class.java)
}
class ConnectionImpl(val connection: Connection) : Connection by connection
class ConnectionImpl(private val connection: Connection) : Connection by connection
@Test
fun `Check blacklisted interface impl`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superinterface java.sql.Connection of net.corda.nodeapi.internal.serialization.CordaClassResolverTests\$ConnectionImpl is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// ConnectionImpl implements blacklisted Connection.
resolver.getRegistration(ConnectionImpl::class.java)
}
interface SubConnection : Connection
class SubConnectionImpl(val subConnection: SubConnection) : SubConnection by subConnection
class SubConnectionImpl(private val subConnection: SubConnection) : SubConnection by subConnection
@Test
fun `Check blacklisted super-interface impl`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superinterface java.sql.Connection of net.corda.nodeapi.internal.serialization.CordaClassResolverTests\$SubConnectionImpl is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// SubConnectionImpl implements SubConnection, which extends the blacklisted Connection.
resolver.getRegistration(SubConnectionImpl::class.java)
}
@Test
fun `Check forcibly allowed`() {
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// LinkedHashSet is allowed for serialization.
resolver.getRegistration(LinkedHashSet::class.java)
}
@ -247,7 +247,7 @@ class CordaClassResolverTests {
fun `Check blacklist precedes CordaSerializable`() {
expectedEx.expect(IllegalStateException::class.java)
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.nodeapi.internal.serialization.CordaClassResolverTests\$CordaSerializableHashSet is blacklisted, so it cannot be used in serialization.")
val resolver = CordaClassResolver(factory, allButBlacklistedContext)
val resolver = CordaClassResolver(allButBlacklistedContext)
// CordaSerializableHashSet is @CordaSerializable, but extends the blacklisted HashSet.
resolver.getRegistration(CordaSerializableHashSet::class.java)
}

View File

@ -5,7 +5,9 @@ import com.esotericsoftware.kryo.KryoSerializable
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.google.common.primitives.Ints
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.*
import net.corda.core.internal.FetchDataFlow
import net.corda.core.serialization.*
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.sequence
@ -16,21 +18,27 @@ import net.corda.testing.TestDependencyInjectionBase
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class KryoTests : TestDependencyInjectionBase() {
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@get:Rule
val expectedEx: ExpectedException = ExpectedException.none()
@Before
fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme(this)) }
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
@ -157,6 +165,26 @@ class KryoTests : TestDependencyInjectionBase() {
override fun toString(): String = "Cyclic($value)"
}
@Test
fun `serialize - deserialize PrivacySalt`() {
val expected = PrivacySalt(byteArrayOf(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32
))
val serializedBytes = expected.serialize(factory, context)
val actual = serializedBytes.deserialize(factory, context)
assertEquals(expected, actual)
}
@Test
fun `all-zero PrivacySalt not allowed`() {
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("Privacy salt should not be all zeros.")
PrivacySalt(ByteArray(32))
}
@CordaSerializable
private object TestSingleton
@ -200,7 +228,7 @@ class KryoTests : TestDependencyInjectionBase() {
}
}
Tmp()
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme(this)) }
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
val context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
@ -209,4 +237,42 @@ class KryoTests : TestDependencyInjectionBase() {
SerializationContext.UseCase.P2P)
pt.serialize(factory, context)
}
}
@Test
fun `serialize - deserialize Exception with suppressed`() {
val exception = IllegalArgumentException("fooBar")
val toBeSuppressedOnSenderSide = IllegalStateException("bazz1")
exception.addSuppressed(toBeSuppressedOnSenderSide)
val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(exception.message, exception2.message)
assertEquals(1, exception2.suppressed.size)
assertNotNull({ exception2.suppressed.find { it.message == toBeSuppressedOnSenderSide.message }})
val toBeSuppressedOnReceiverSide = IllegalStateException("bazz2")
exception2.addSuppressed(toBeSuppressedOnReceiverSide)
assertTrue { exception2.suppressed.contains(toBeSuppressedOnReceiverSide) }
assertEquals(2, exception2.suppressed.size)
}
@Test
fun `serialize - deserialize Exception no suppressed`() {
val exception = IllegalArgumentException("fooBar")
val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(exception.message, exception2.message)
assertEquals(0, exception2.suppressed.size)
val toBeSuppressedOnReceiverSide = IllegalStateException("bazz2")
exception2.addSuppressed(toBeSuppressedOnReceiverSide)
assertEquals(1, exception2.suppressed.size)
assertTrue { exception2.suppressed.contains(toBeSuppressedOnReceiverSide) }
}
@Test
fun `serialize - deserialize HashNotFound`() {
val randomHash = SecureHash.randomSHA256()
val exception = FetchDataFlow.HashNotFound(randomHash)
val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(randomHash, exception2.requested)
}
}

View File

@ -4,10 +4,8 @@ import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.io.Output
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.testing.TestDependencyInjectionBase
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
@ -16,18 +14,13 @@ import java.io.ByteArrayOutputStream
class SerializationTokenTest : TestDependencyInjectionBase() {
lateinit var factory: SerializationFactory
lateinit var context: SerializationContext
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@Before
fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme(this)) }
context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
factory = SerializationDefaults.SERIALIZATION_FACTORY
context = SerializationDefaults.CHECKPOINT_CONTEXT.withWhitelisted(SingletonSerializationToken::class.java)
}
// Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized
@ -42,7 +35,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() {
override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size
}
private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, mock<ServiceHub>())
private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, mock())
@Test
fun `write token and read tokenizable`() {
@ -97,7 +90,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() {
val context = serializeAsTokenContext(tokenizableBefore)
val testContext = this.context.withTokenContext(context)
val kryo: Kryo = DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(factory, this.context)))
val kryo: Kryo = DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(this.context)))
val stream = ByteArrayOutputStream()
Output(stream).use {
it.write(KryoHeaderV0_1.bytes)

View File

@ -1,10 +1,16 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.codec.Data
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
class TestSerializationOutput(
private val verbose: Boolean,
serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) {
serializerFactory: SerializerFactory = testDefaultFactory())
: SerializationOutput(serializerFactory) {
override fun writeSchema(schema: Schema, data: Data) {
if (verbose) println(schema)

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializable
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@ -18,7 +19,7 @@ class DeserializeAndReturnEnvelopeTests {
val a = A(10, "20")
val factory = SerializerFactory()
val factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
@ -34,7 +35,7 @@ class DeserializeAndReturnEnvelopeTests {
val b = B(A(10, "20"), 30.0F)
val factory = SerializerFactory()
val factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
@ -43,4 +44,22 @@ class DeserializeAndReturnEnvelopeTests {
assertNotEquals(null, obj.envelope.schema.types.find { it.name == classTestName("A") })
assertNotEquals(null, obj.envelope.schema.types.find { it.name == classTestName("B") })
}
@Test
fun unannotatedInterfaceIsNotInSchema() {
@CordaSerializable
data class Foo(val bar: Int) : Comparable<Foo> {
override fun compareTo(other: Foo): Int = bar.compareTo(other.bar)
}
val a = Foo(123)
val factory = testDefaultFactoryWithWhitelist()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assertTrue(obj.obj is Foo)
assertEquals(1, obj.envelope.schema.types.size)
assertNotEquals(null, obj.envelope.schema.types.find { it.name == classTestName("Foo") })
assertEquals(null, obj.envelope.schema.types.find { it.name == "java.lang.Comparable<${classTestName("Foo")}>" })
}
}

View File

@ -0,0 +1,118 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.junit.Test
import java.util.*
class DeserializeCollectionTests {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
val sf = testDefaultFactory()
@Test
fun mapTest() {
data class C(val c: Map<String, Int>)
val c = C (mapOf("A" to 1, "B" to 2))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected=java.io.NotSerializableException::class)
fun abstractMapFromMapOf() {
data class C(val c: AbstractMap<String, Int>)
val c = C (mapOf("A" to 1, "B" to 2) as AbstractMap)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected=java.io.NotSerializableException::class)
fun abstractMapFromTreeMap() {
data class C(val c: AbstractMap<String, Int>)
val c = C (TreeMap(mapOf("A" to 1, "B" to 2)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun sortedMapTest() {
data class C(val c: SortedMap<String, Int>)
val c = C(sortedMapOf ("A" to 1, "B" to 2))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun navigableMapTest() {
data class C(val c: NavigableMap<String, Int>)
val c = C(TreeMap (mapOf("A" to 1, "B" to 2)).descendingMap())
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected=java.io.NotSerializableException::class)
fun dictionaryTest() {
data class C(val c: Dictionary<String, Int>)
val v : Hashtable<String, Int> = Hashtable()
v.put ("a", 1)
v.put ("b", 2)
val c = C(v)
// expected to throw
TestSerializationOutput(VERBOSE, sf).serialize(c)
}
@Test(expected=java.lang.IllegalArgumentException::class)
fun hashtableTest() {
data class C(val c: Hashtable<String, Int>)
val v : Hashtable<String, Int> = Hashtable()
v.put ("a", 1)
v.put ("b", 2)
val c = C(v)
// expected to throw
TestSerializationOutput(VERBOSE, sf).serialize(c)
}
@Test(expected=java.lang.IllegalArgumentException::class)
fun hashMapTest() {
data class C(val c : HashMap<String, Int>)
val c = C (HashMap (mapOf("A" to 1, "B" to 2)))
// expect this to throw
TestSerializationOutput(VERBOSE, sf).serialize(c)
}
@Test(expected=java.lang.IllegalArgumentException::class)
fun weakHashMapTest() {
data class C(val c : WeakHashMap<String, Int>)
val c = C (WeakHashMap (mapOf("A" to 1, "B" to 2)))
TestSerializationOutput(VERBOSE, sf).serialize(c)
}
@Test
fun concreteTreeMapTest() {
data class C(val c: TreeMap<String, Int>)
val c = C(TreeMap (mapOf("A" to 1, "B" to 3)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test
fun concreteLinkedHashMapTest() {
data class C(val c : LinkedHashMap<String, Int>)
val c = C (LinkedHashMap (mapOf("A" to 1, "B" to 2)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
}

View File

@ -8,7 +8,7 @@ import net.corda.nodeapi.internal.serialization.carpenter.*
// those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent
// versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This
// replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath
class DeserializeNeedingCarpentrySimpleTypesTest {
class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase() {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
@ -16,12 +16,12 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
private const val VERBOSE = false
}
val sf = SerializerFactory()
val sf2 = SerializerFactory()
val sf = testDefaultFactory()
val sf2 = testDefaultFactory()
@Test
fun singleInt() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"int" to NonNullableField(Integer::class.javaPrimitiveType!!)
)))
@ -41,7 +41,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleIntNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"int" to NullableField(Integer::class.java)
)))
@ -57,7 +57,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleIntNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"int" to NullableField(Integer::class.java)
)))
@ -73,7 +73,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleChar() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"char" to NonNullableField(Character::class.javaPrimitiveType!!)
)))
@ -86,7 +86,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleCharNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"char" to NullableField(Character::class.javaObjectType)
)))
@ -99,7 +99,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleCharNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"char" to NullableField(java.lang.Character::class.java)
)))
@ -112,7 +112,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleLong() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"long" to NonNullableField(Long::class.javaPrimitiveType!!)
)))
@ -126,7 +126,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleLongNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"long" to NullableField(Long::class.javaObjectType)
)))
@ -140,7 +140,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleLongNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"long" to NullableField(Long::class.javaObjectType)
)))
@ -153,7 +153,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleBoolean() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"boolean" to NonNullableField(Boolean::class.javaPrimitiveType!!)
)))
@ -166,7 +166,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleBooleanNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"boolean" to NullableField(Boolean::class.javaObjectType)
)))
@ -179,7 +179,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleBooleanNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"boolean" to NullableField(Boolean::class.javaObjectType)
)))
@ -192,7 +192,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleDouble() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"double" to NonNullableField(Double::class.javaPrimitiveType!!)
)))
@ -205,7 +205,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleDoubleNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"double" to NullableField(Double::class.javaObjectType)
)))
@ -218,7 +218,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleDoubleNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"double" to NullableField(Double::class.javaObjectType)
)))
@ -231,7 +231,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleShort() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"short" to NonNullableField(Short::class.javaPrimitiveType!!)
)))
@ -244,7 +244,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleShortNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"short" to NullableField(Short::class.javaObjectType)
)))
@ -257,7 +257,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleShortNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"short" to NullableField(Short::class.javaObjectType)
)))
@ -270,7 +270,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleFloat() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"float" to NonNullableField(Float::class.javaPrimitiveType!!)
)))
@ -283,7 +283,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleFloatNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"float" to NullableField(Float::class.javaObjectType)
)))
@ -296,7 +296,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleFloatNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"float" to NullableField(Float::class.javaObjectType)
)))
@ -309,7 +309,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleByte() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"byte" to NonNullableField(Byte::class.javaPrimitiveType!!)
)))
@ -324,7 +324,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleByteNullable() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"byte" to NullableField(Byte::class.javaObjectType)
)))
@ -339,7 +339,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun singleByteNullableNull() {
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf(
"byte" to NullableField(Byte::class.javaObjectType)
)))
@ -353,11 +353,10 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun simpleTypeKnownInterface() {
val clazz = ClassCarpenter().build (ClassSchema(
"oneType", mapOf("name" to NonNullableField(String::class.java)),
testName(), mapOf("name" to NonNullableField(String::class.java)),
interfaces = listOf (I::class.java)))
val testVal = "Some Person"
val classInstance = clazz.constructors[0].newInstance(testVal)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
val deserializedObj = DeserializationInput(sf2).deserialize(serialisedBytes)
@ -368,7 +367,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
@Test
fun manyTypes() {
val manyClass = ClassCarpenter().build (ClassSchema("many", mapOf(
val manyClass = ClassCarpenter().build (ClassSchema(testName(), mapOf(
"intA" to NonNullableField (Int::class.java),
"intB" to NullableField (Integer::class.java),
"intC" to NullableField (Integer::class.java),
@ -397,7 +396,7 @@ class DeserializeNeedingCarpentrySimpleTypesTest {
"byteB" to NullableField (Byte::class.javaObjectType),
"byteC" to NullableField (Byte::class.javaObjectType))))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(
val serialisedBytes = TestSerializationOutput(VERBOSE, factory).serialize(
manyClass.constructors.first().newInstance(
1, 2, null,
"a", "b", null,

View File

@ -1,24 +1,25 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializable
import org.junit.Test
import kotlin.test.*
import net.corda.nodeapi.internal.serialization.carpenter.*
import net.corda.nodeapi.internal.serialization.AllWhitelist
@CordaSerializable
interface I {
fun getName() : String
}
/**
* These tests work by having the class carpenter build the classes we serialise and then deserialise them
* within the context of a second serialiser factory. The second factory is required as the first, having
* been used to serialise the class, will have cached a copy of the class and will thus bypass the need
* to pull it out of the class loader.
*
* However, those classes don't exist within the system's Class Loader and thus the deserialiser will be forced
* to carpent versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This
* replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath
*/
class DeserializeNeedingCarpentryTests {
// These tests work by having the class carpenter build the classes we serialise and then deserialise them
// within the context of a second serialiser factory. The second factory is required as the first, having
// been used to serialise the class, will have cached a copy of the class and will thus bypass the need
// to pull it out of the class loader.
//
// However, those classes don't exist within the system's Class Loader and thus the deserialiser will be forced
// to carpent versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This
// replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath
class DeserializeNeedingCarpentryTests : AmqpCarpenterBase() {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
@ -26,13 +27,13 @@ class DeserializeNeedingCarpentryTests {
private const val VERBOSE = false
}
val sf1 = SerializerFactory()
val sf2 = SerializerFactory()
val sf1 = testDefaultFactory()
val sf2 = testDefaultFactoryWithWhitelist() // Deserialize with whitelisting on to check that `CordaSerializable` annotation present.
@Test
fun verySimpleType() {
val testVal = 10
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf("a" to NonNullableField(Int::class.java))))
val classInstance = clazz.constructors[0].newInstance(testVal)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf1).serialize(classInstance)
@ -66,16 +67,20 @@ class DeserializeNeedingCarpentryTests {
val testValA = 10
val testValB = 20
val testValC = 20
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
val clazz = ClassCarpenter().build(ClassSchema("${testName()}_clazz",
mapOf("a" to NonNullableField(Int::class.java))))
val concreteA = clazz.constructors[0].newInstance(testValA)
val concreteB = clazz.constructors[0].newInstance(testValB)
val concreteC = clazz.constructors[0].newInstance(testValC)
val deserialisedA = DeserializationInput(sf2).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(concreteA))
val deserialisedA = DeserializationInput(sf2).deserialize(
TestSerializationOutput(VERBOSE, sf1).serialize(concreteA))
assertEquals (testValA, deserialisedA::class.java.getMethod("getA").invoke(deserialisedA))
val deserialisedB = DeserializationInput(sf2).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(concreteB))
val deserialisedB = DeserializationInput(sf2).deserialize(
TestSerializationOutput(VERBOSE, sf1).serialize(concreteB))
assertEquals (testValB, deserialisedA::class.java.getMethod("getA").invoke(deserialisedB))
assertEquals (deserialisedA::class.java, deserialisedB::class.java)
@ -84,8 +89,10 @@ class DeserializeNeedingCarpentryTests {
// won't already exist and it will be carpented a second time showing that when A and B are the
// same underlying class that we didn't create a second instance of the class with the
// second deserialisation
val lsf = SerializerFactory()
val deserialisedC = DeserializationInput(lsf).deserialize(TestSerializationOutput(VERBOSE, lsf).serialize(concreteC))
val lfactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val deserialisedC = DeserializationInput(lfactory).deserialize(
TestSerializationOutput(VERBOSE, lfactory).serialize(concreteC))
assertEquals (testValC, deserialisedC::class.java.getMethod("getA").invoke(deserialisedC))
assertNotEquals (deserialisedA::class.java, deserialisedC::class.java)
assertNotEquals (deserialisedB::class.java, deserialisedC::class.java)
@ -94,7 +101,7 @@ class DeserializeNeedingCarpentryTests {
@Test
fun simpleTypeKnownInterface() {
val clazz = ClassCarpenter().build (ClassSchema(
"oneType", mapOf("name" to NonNullableField(String::class.java)),
testName(), mapOf("name" to NonNullableField(String::class.java)),
interfaces = listOf (I::class.java)))
val testVal = "Some Person"
val classInstance = clazz.constructors[0].newInstance(testVal)
@ -108,8 +115,9 @@ class DeserializeNeedingCarpentryTests {
@Test
fun arrayOfTypes() {
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
val clazz = ClassCarpenter().build(ClassSchema(testName(), mapOf("a" to NonNullableField(Int::class.java))))
@CordaSerializable
data class Outer (val a : Array<Any>)
val outer = Outer (arrayOf (
@ -142,8 +150,8 @@ class DeserializeNeedingCarpentryTests {
fun reusedClasses() {
val cc = ClassCarpenter()
val innerType = cc.build(ClassSchema("inner", mapOf("a" to NonNullableField(Int::class.java))))
val outerType = cc.build(ClassSchema("outer", mapOf("a" to NonNullableField(innerType))))
val innerType = cc.build(ClassSchema("${testName()}.inner", mapOf("a" to NonNullableField(Int::class.java))))
val outerType = cc.build(ClassSchema("${testName()}.outer", mapOf("a" to NonNullableField(innerType))))
val inner = innerType.constructors[0].newInstance(1)
val outer = outerType.constructors[0].newInstance(innerType.constructors[0].newInstance(2))
@ -180,6 +188,7 @@ class DeserializeNeedingCarpentryTests {
val nestedClass = cc.build (ClassSchema("nestedType",
mapOf("name" to NonNullableField(String::class.java))))
@CordaSerializable
data class outer(val a: Any, val b: Any)
val classInstance = outer (
@ -195,10 +204,11 @@ class DeserializeNeedingCarpentryTests {
@Test
fun listOfType() {
val unknownClass = ClassCarpenter().build (ClassSchema("unknownClass", mapOf(
val unknownClass = ClassCarpenter().build (ClassSchema(testName(), mapOf(
"v1" to NonNullableField(Int::class.java),
"v2" to NonNullableField(Int::class.java))))
@CordaSerializable
data class outer (val l : List<Any>)
val toSerialise = outer (listOf (
unknownClass.constructors.first().newInstance(1, 2),
@ -223,7 +233,7 @@ class DeserializeNeedingCarpentryTests {
"gen.Interface",
mapOf("age" to NonNullableField (Int::class.java))))
val concreteClass = cc.build (ClassSchema ("gen.Class", mapOf(
val concreteClass = cc.build (ClassSchema (testName(), mapOf(
"age" to NonNullableField (Int::class.java),
"name" to NonNullableField(String::class.java)),
interfaces = listOf (I::class.java, interfaceClass)))

View File

@ -16,30 +16,30 @@ class DeserializeSimpleTypesTests {
private const val VERBOSE = false
}
val sf1 = SerializerFactory()
val sf2 = SerializerFactory()
val sf1 = testDefaultFactory()
val sf2 = testDefaultFactory()
@Test
fun testChar() {
data class C(val c: Char)
var deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('c')))
var deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('c')))
assertEquals('c', deserializedC.c)
// CYRILLIC CAPITAL LETTER YU (U+042E)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('Ю')))
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('Ю')))
assertEquals('Ю', deserializedC.c)
// ARABIC LETTER FEH WITH DOT BELOW (U+06A3)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('ڣ')))
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('ڣ')))
assertEquals('ڣ', deserializedC.c)
// ARABIC LETTER DAD WITH DOT BELOW (U+06FB)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('ۻ')))
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('ۻ')))
assertEquals('ۻ', deserializedC.c)
// BENGALI LETTER AA (U+0986)
deserializedC = DeserializationInput().deserialize(SerializationOutput().serialize(C('আ')))
deserializedC = DeserializationInput(sf1).deserialize(SerializationOutput(sf1).serialize(C('আ')))
assertEquals('আ', deserializedC.c)
}
@ -49,8 +49,8 @@ class DeserializeSimpleTypesTests {
data class C(val c: Character)
val c = C(Character('c'))
val serialisedC = SerializationOutput().serialize(c)
val deserializedC = DeserializationInput().deserialize(serialisedC)
val serialisedC = SerializationOutput(sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c, deserializedC.c)
}
@ -60,8 +60,8 @@ class DeserializeSimpleTypesTests {
data class C(val c: Char?)
val c = C(null)
val serialisedC = SerializationOutput().serialize(c)
val deserializedC = DeserializationInput().deserialize(serialisedC)
val serialisedC = SerializationOutput(sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
assertEquals(c.c, deserializedC.c)
}

View File

@ -34,6 +34,11 @@ class DeserializedParameterizedTypeTests {
verify("java.util.Map<java.lang.String, java.lang.Integer> ")
}
@Test
fun `test list of commands`() {
verify("java.util.List<net.corda.core.contracts.Command<?>>")
}
@Test(expected = NotSerializableException::class)
fun `test trailing text`() {
verify("java.util.Map<java.lang.String, java.lang.Integer>foo")

View File

@ -0,0 +1,461 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import kotlin.test.assertEquals
// To regenerate any of the binary test files do the following
//
// 1. Uncomment the code where the original form of the class is defined in the test
// 2. Comment out the rest of the test
// 3. Run the test
// 4. Using the printed path copy that file to the resources directory
// 5. Comment back out the generation code and uncomment the actual test
class EvolvabilityTests {
@Test
fun simpleOrderSwapSameType() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapSameType")
val f = File(path.toURI())
val A = 1
val B = 2
// Original version of the class for the serialised version of this class
//
// data class C (val a: Int, val b: Int)
// val sc = SerializationOutput(sf).serialize(C(A, B))
// f.writeBytes(sc.bytes)
// new version of the class, in this case the order of the parameters has been swapped
data class C (val b: Int, val a: Int)
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(A, deserializedC.a)
assertEquals(B, deserializedC.b)
}
@Test
fun simpleOrderSwapDifferentType() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapDifferentType")
val f = File(path.toURI())
val A = 1
val B = "two"
// Original version of the class as it was serialised
//
// data class C (val a: Int, val b: String)
// val sc = SerializationOutput(sf).serialize(C(A, B))
// f.writeBytes(sc.bytes)
// new version of the class, in this case the order of the parameters has been swapped
data class C (val b: String, val a: Int)
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals(A, deserializedC.a)
assertEquals(B, deserializedC.b)
}
@Test
fun addAdditionalParamNotMandatory() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAdditionalParamNotMandatory")
val f = File(path.toURI())
val A = 1
// Original version of the class as it was serialised
//
// data class C(val a: Int)
// val sc = SerializationOutput(sf).serialize(C(A))
// f.writeBytes(sc.bytes)
// println ("Path = $path")
data class C (val a: Int, val b: Int?)
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals (A, deserializedC.a)
assertEquals (null, deserializedC.b)
}
@Test(expected = NotSerializableException::class)
fun addAdditionalParam() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAdditionalParam")
val f = File(path.toURI())
@Suppress("UNUSED_VARIABLE")
val A = 1
// Original version of the class as it was serialised
//
// data class C(val a: Int)
// val sc = SerializationOutput(sf).serialize(C(A))
// f.writeBytes(sc.bytes)
// println ("Path = $path")
// new version of the class, in this case a new parameter has been added (b)
data class C (val a: Int, val b: Int)
val sc2 = f.readBytes()
// Expected to throw as we can't construct the new type as it contains a newly
// added parameter that isn't optional, i.e. not nullable and there isn't
// a constructor that takes the old parameters
DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
}
@Suppress("UNUSED_VARIABLE")
@Test
fun removeParameters() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters")
val f = File(path.toURI())
val A = 1
val B = "two"
val C = "three"
val D = 4
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C, D))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
data class CC (val b: String, val d: Int)
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (B, deserializedCC.b)
assertEquals (D, deserializedCC.d)
}
@Suppress("UNUSED_VARIABLE")
@Test
fun addAndRemoveParameters() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAndRemoveParameters")
val f = File(path.toURI())
val A = 1
val B = "two"
val C = "three"
val D = 4
val E = null
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: String, val c: String, val d: Int)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C, D))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
data class CC(val a: Int, val e: Boolean?, val d: Int)
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals(A, deserializedCC.a)
assertEquals(E, deserializedCC.e)
assertEquals(D, deserializedCC.d)
}
@Test
fun addMandatoryFieldWithAltConstructor() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addMandatoryFieldWithAltConstructor")
val f = File(path.toURI())
val A = 1
// Original version of the class as it was serialised
//
// data class CC(val a: Int)
// val scc = SerializationOutput(sf).serialize(CC(A))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
@Suppress("UNUSED")
data class CC (val a: Int, val b: String) {
@DeprecatedConstructorForDeserialization(1)
constructor (a: Int) : this (a, "hello")
}
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (A, deserializedCC.a)
assertEquals ("hello", deserializedCC.b)
}
@Test(expected = NotSerializableException::class)
@Suppress("UNUSED")
fun addMandatoryFieldWithAltConstructorUnAnnotated() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource(
"EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated")
val f = File(path.toURI())
@Suppress("UNUSED_VARIABLE")
val A = 1
// Original version of the class as it was serialised
//
// data class CC(val a: Int)
// val scc = SerializationOutput(sf).serialize(CC(A))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
data class CC (val a: Int, val b: String) {
// constructor annotation purposefully omitted
constructor (a: Int) : this (a, "hello")
}
// we expect this to throw as we should not find any constructors
// capable of dealing with this
DeserializationInput(sf).deserialize(SerializedBytes<CC>(f.readBytes()))
}
@Test
fun addMandatoryFieldWithAltReorderedConstructor() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource(
"EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor")
val f = File(path.toURI())
val A = 1
val B = 100
val C = "This is not a banana"
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: Int, val c: String)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
@Suppress("UNUSED")
data class CC (val a: Int, val b: Int, val c: String, val d: String) {
// ensure none of the original parameters align with the initial
// construction order
@DeprecatedConstructorForDeserialization(1)
constructor (c: String, a: Int, b: Int) : this (a, b, c, "wibble")
}
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (A, deserializedCC.a)
assertEquals (B, deserializedCC.b)
assertEquals (C, deserializedCC.c)
assertEquals ("wibble", deserializedCC.d)
}
@Test
fun addMandatoryFieldWithAltReorderedConstructorAndRemoval() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource(
"EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval")
val f = File(path.toURI())
val A = 1
@Suppress("UNUSED_VARIABLE")
val B = 100
val C = "This is not a banana"
// Original version of the class as it was serialised
//
// data class CC(val a: Int, val b: Int, val c: String)
// val scc = SerializationOutput(sf).serialize(CC(A, B, C))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// b is removed, d is added
data class CC (val a: Int, val c: String, val d: String) {
// ensure none of the original parameters align with the initial
// construction order
@Suppress("UNUSED")
@DeprecatedConstructorForDeserialization(1)
constructor (c: String, a: Int) : this (a, c, "wibble")
}
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (A, deserializedCC.a)
assertEquals (C, deserializedCC.c)
assertEquals ("wibble", deserializedCC.d)
}
@Test
fun multiVersion() {
val sf = testDefaultFactory()
val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.1")
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2")
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3")
@Suppress("UNUSED_VARIABLE")
val f = File(path1.toURI())
val a = 100
val b = 200
val c = 300
val d = 400
// Original version of the class as it was serialised
//
// Version 1:
// data class C (val a: Int, val b: Int)
// Version 2 - add param c
// data class C (val c: Int, val b: Int, val a: Int)
// Version 3 - add param d
// data class C (val b: Int, val c: Int, val d: Int, val a: Int)
//
// val scc = SerializationOutput(sf).serialize(C(b, c, d, a))
// f.writeBytes(scc.bytes)
// println ("Path = $path1")
@Suppress("UNUSED")
data class C (val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
@DeprecatedConstructorForDeserialization(1)
constructor (b: Int, a: Int) : this (-1, -1, b, a, -1)
@DeprecatedConstructorForDeserialization(2)
constructor (a: Int, c: Int, b: Int) : this (-1, c, b, a, -1)
@DeprecatedConstructorForDeserialization(3)
constructor (a: Int, b: Int, c: Int, d: Int) : this (-1, c, b, a, d)
}
val sb1 = File(path1.toURI()).readBytes()
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
assertEquals(a, db1.a)
assertEquals(b, db1.b)
assertEquals(-1, db1.c)
assertEquals(-1, db1.d)
assertEquals(-1, db1.e)
val sb2 = File(path2.toURI()).readBytes()
val db2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb2))
assertEquals(a, db2.a)
assertEquals(b, db2.b)
assertEquals(c, db2.c)
assertEquals(-1, db2.d)
assertEquals(-1, db2.e)
val sb3 = File(path3.toURI()).readBytes()
val db3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb3))
assertEquals(a, db3.a)
assertEquals(b, db3.b)
assertEquals(c, db3.c)
assertEquals(d, db3.d)
assertEquals(-1, db3.e)
}
@Test
fun changeSubType() {
val sf = testDefaultFactory()
val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.changeSubType")
val f = File(path.toURI())
val oa = 100
val ia = 200
// Original version of the class as it was serialised
//
// data class Inner (val a: Int)
// data class Outer (val a: Int, val b: Inner)
// val scc = SerializationOutput(sf).serialize(Outer(oa, Inner (ia)))
// f.writeBytes(scc.bytes)
// println ("Path = $path")
// Add a parameter to inner but keep outer unchanged
data class Inner (val a: Int, val b: String?)
data class Outer (val a: Int, val b: Inner)
val sc2 = f.readBytes()
val outer = DeserializationInput(sf).deserialize(SerializedBytes<Outer>(sc2))
assertEquals (oa, outer.a)
assertEquals (ia, outer.b.a)
assertEquals (null, outer.b.b)
}
@Test
fun multiVersionWithRemoval() {
val sf = testDefaultFactory()
val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.1")
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.2")
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.3")
@Suppress("UNUSED_VARIABLE")
val a = 100
val b = 200
val c = 300
val d = 400
val e = 500
val f = 600
// Original version of the class as it was serialised
//
// Version 1:
// data class C (val a: Int, val b: Int, val c: Int)
// Version 2 - add param c
// data class C (val b: Int, val c: Int, val d: Int, val e: Int)
// Version 3 - add param d
// data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int)
//
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f))
// File(path1.toURI()).writeBytes(scc.bytes)
// println ("Path = $path1")
@Suppress("UNUSED")
data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
@DeprecatedConstructorForDeserialization(1)
constructor (b: Int, c: Int) : this (b, c, -1, -1, -1, -1)
@DeprecatedConstructorForDeserialization(2)
constructor (b: Int, c: Int, d: Int) : this (b, c, d, -1, -1, -1)
@DeprecatedConstructorForDeserialization(3)
constructor (b: Int, c: Int, d: Int, e: Int) : this (b, c, d, e, -1, -1)
@DeprecatedConstructorForDeserialization(4)
constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this (b, c, d, e, f, -1)
}
val sb1 = File(path1.toURI()).readBytes()
val db1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb1))
assertEquals(b, db1.b)
assertEquals(c, db1.c)
assertEquals(-1, db1.d) // must not be set by calling constructor 2 by mistake
assertEquals(-1, db1.e)
assertEquals(-1, db1.f)
assertEquals(-1, db1.g)
val sb2 = File(path2.toURI()).readBytes()
val db2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb2))
assertEquals(b, db2.b)
assertEquals(c, db2.c)
assertEquals(d, db2.d)
assertEquals(e, db2.e)
assertEquals(-1, db2.f)
assertEquals(-1, db1.g)
val sb3 = File(path3.toURI()).readBytes()
val db3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(sb3))
assertEquals(b, db3.b)
assertEquals(c, db3.c)
assertEquals(d, db3.d)
assertEquals(e, db3.e)
assertEquals(f, db3.f)
assertEquals(-1, db3.g)
}
}

View File

@ -14,19 +14,24 @@ import net.corda.nodeapi.RPCException
import net.corda.nodeapi.internal.serialization.AbstractAMQPSerializationScheme
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive
import net.corda.nodeapi.internal.serialization.amqp.custom.*
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.testing.BOB_IDENTITY
import net.corda.testing.MEGA_CORP
import net.corda.testing.MEGA_CORP_PUBKEY
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl
import org.junit.Ignore
import org.junit.Test
import java.io.IOException
import java.io.NotSerializableException
import java.math.BigDecimal
import java.nio.ByteBuffer
import java.time.Instant
import java.time.*
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalUnit
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@ -94,35 +99,35 @@ class SerializationOutputTests {
data class SortedSetWrapper(val set: SortedSet<Int>)
open class InheritedGeneric<X>(val foo: X)
open class InheritedGeneric<out X>(val foo: X)
data class ExtendsGeneric(val bar: Int, val pub: String) : InheritedGeneric<String>(pub)
interface GenericInterface<X> {
interface GenericInterface<out X> {
val pub: X
}
data class ImplementsGenericString(val bar: Int, override val pub: String) : GenericInterface<String>
data class ImplementsGenericX<Y>(val bar: Int, override val pub: Y) : GenericInterface<Y>
data class ImplementsGenericX<out Y>(val bar: Int, override val pub: Y) : GenericInterface<Y>
abstract class AbstractGenericX<Z> : GenericInterface<Z>
abstract class AbstractGenericX<out Z> : GenericInterface<Z>
data class InheritGenericX<A>(val duke: Double, override val pub: A) : AbstractGenericX<A>()
data class InheritGenericX<out A>(val duke: Double, override val pub: A) : AbstractGenericX<A>()
data class CapturesGenericX(val foo: GenericInterface<String>)
object KotlinObject
class Mismatch(fred: Int) {
val ginger: Int = fred
private val ginger: Int = fred
override fun equals(other: Any?): Boolean = (other as? Mismatch)?.ginger == ginger
override fun hashCode(): Int = ginger
}
class MismatchType(fred: Long) {
val ginger: Int = fred.toInt()
private val ginger: Int = fred.toInt()
override fun equals(other: Any?): Boolean = (other as? MismatchType)?.ginger == ginger
override fun hashCode(): Int = ginger
@ -136,8 +141,10 @@ class SerializationOutputTests {
data class PolymorphicProperty(val foo: FooInterface?)
private fun serdes(obj: Any,
factory: SerializerFactory = SerializerFactory(),
freshDeserializationFactory: SerializerFactory = SerializerFactory(),
factory: SerializerFactory = SerializerFactory (
AllWhitelist, ClassLoader.getSystemClassLoader()),
freshDeserializationFactory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): Any {
val ser = SerializationOutput(factory)
@ -242,7 +249,7 @@ class SerializationOutputTests {
@Test(expected = IllegalArgumentException::class)
fun `test dislike of HashMap`() {
val obj = WrapHashMap(HashMap<String, String>())
val obj = WrapHashMap(HashMap())
serdes(obj)
}
@ -285,13 +292,13 @@ class SerializationOutputTests {
@Test(expected = NotSerializableException::class)
fun `test whitelist`() {
val obj = Woo2(4)
serdes(obj, SerializerFactory(EmptyWhitelist))
serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()))
}
@Test
fun `test annotation whitelisting`() {
val obj = AnnotatedWoo(5)
serdes(obj, SerializerFactory(EmptyWhitelist))
serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()))
}
@Test(expected = NotSerializableException::class)
@ -352,9 +359,9 @@ class SerializationOutputTests {
serdes(obj)
}
@Test(expected = NotSerializableException::class)
@Test
fun `test TreeMap property`() {
val obj = TreeMapWrapper(TreeMap<Int, Foo>())
val obj = TreeMapWrapper(TreeMap())
obj.tree[456] = Foo("Fred", 123)
serdes(obj)
}
@ -387,10 +394,10 @@ class SerializationOutputTests {
@Test
fun `test custom serializers on public key`() {
val factory = SerializerFactory()
factory.register(PublicKeySerializer)
val factory2 = SerializerFactory()
factory2.register(PublicKeySerializer)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
val obj = MEGA_CORP_PUBKEY
serdes(obj, factory, factory2)
}
@ -398,16 +405,16 @@ class SerializationOutputTests {
@Test
fun `test annotation is inherited`() {
val obj = InheritAnnotation("blah")
serdes(obj, SerializerFactory(EmptyWhitelist))
serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()))
}
@Test
fun `test throwables serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory2))
val t = IllegalAccessException("message").fillInStackTrace()
val desThrowable = serdes(t, factory, factory2, false) as Throwable
@ -416,11 +423,11 @@ class SerializationOutputTests {
@Test
fun `test complex throwables serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory2))
try {
try {
@ -434,11 +441,10 @@ class SerializationOutputTests {
}
}
fun assertSerializedThrowableEquivalent(t: Throwable, desThrowable: Throwable) {
private fun assertSerializedThrowableEquivalent(t: Throwable, desThrowable: Throwable) {
assertTrue(desThrowable is CordaRuntimeException) // Since we don't handle the other case(s) yet
if (desThrowable is CordaRuntimeException) {
assertEquals("${t.javaClass.name}: ${t.message}", desThrowable.message)
assertTrue(desThrowable is CordaRuntimeException)
assertTrue(Objects.deepEquals(t.stackTrace, desThrowable.stackTrace))
assertEquals(t.suppressed.size, desThrowable.suppressed.size)
t.suppressed.zip(desThrowable.suppressed).forEach { (before, after) -> assertSerializedThrowableEquivalent(before, after) }
@ -447,11 +453,11 @@ class SerializationOutputTests {
@Test
fun `test suppressed throwables serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory2))
try {
try {
@ -469,11 +475,11 @@ class SerializationOutputTests {
@Test
fun `test flow corda exception subclasses serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory2))
val obj = FlowException("message").fillInStackTrace()
serdes(obj, factory, factory2)
@ -481,11 +487,11 @@ class SerializationOutputTests {
@Test
fun `test RPC corda exception subclasses serialize`() {
val factory = SerializerFactory()
factory.register(ThrowableSerializer(factory))
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(ThrowableSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(factory2))
val obj = RPCException("message").fillInStackTrace()
serdes(obj, factory, factory2)
@ -512,8 +518,6 @@ class SerializationOutputTests {
override fun verify(tx: LedgerTransaction) {
}
override val legalContractReference: SecureHash = SecureHash.Companion.sha256("FooContractLegal")
}
class FooState : ContractState {
@ -525,12 +529,12 @@ class SerializationOutputTests {
@Test
fun `test transaction state`() {
val state = TransactionState<FooState>(FooState(), MEGA_CORP)
val state = TransactionState(FooState(), MEGA_CORP)
val factory = SerializerFactory()
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
AbstractAMQPSerializationScheme.registerCustomSerializers(factory)
val factory2 = SerializerFactory()
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
AbstractAMQPSerializationScheme.registerCustomSerializers(factory2)
val desState = serdes(state, factory, factory2, expectedEqual = false, expectDeserializedEqual = false)
@ -542,11 +546,11 @@ class SerializationOutputTests {
@Test
fun `test currencies serialize`() {
val factory = SerializerFactory()
factory.register(CurrencySerializer)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
val factory2 = SerializerFactory()
factory2.register(CurrencySerializer)
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
val obj = Currency.getInstance("USD")
serdes(obj, factory, factory2)
@ -554,11 +558,11 @@ class SerializationOutputTests {
@Test
fun `test big decimals serialize`() {
val factory = SerializerFactory()
factory.register(BigDecimalSerializer)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer)
val factory2 = SerializerFactory()
factory2.register(BigDecimalSerializer)
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer)
val obj = BigDecimal("100000000000000000000000000000.00")
serdes(obj, factory, factory2)
@ -566,23 +570,195 @@ class SerializationOutputTests {
@Test
fun `test instants serialize`() {
val factory = SerializerFactory()
factory.register(InstantSerializer(factory))
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(InstantSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(factory2))
val obj = Instant.now()
serdes(obj, factory, factory2)
}
@Test
fun `test StateRef serialize`() {
val factory = SerializerFactory()
factory.register(InstantSerializer(factory))
fun `test durations serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.DurationSerializer(factory))
val factory2 = SerializerFactory()
factory2.register(InstantSerializer(factory2))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.DurationSerializer(factory2))
val obj = Duration.of(1000000L, ChronoUnit.MILLIS)
serdes(obj, factory, factory2)
}
@Test
fun `test local date serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalDateSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalDateSerializer(factory2))
val obj = LocalDate.now()
serdes(obj, factory, factory2)
}
@Test
fun `test local time serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalTimeSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalTimeSerializer(factory2))
val obj = LocalTime.now()
serdes(obj, factory, factory2)
}
@Test
fun `test local date time serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalDateTimeSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.LocalDateTimeSerializer(factory2))
val obj = LocalDateTime.now()
serdes(obj, factory, factory2)
}
@Test
fun `test zoned date time serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.ZonedDateTimeSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.ZonedDateTimeSerializer(factory2))
val obj = ZonedDateTime.now()
serdes(obj, factory, factory2)
}
@Test
fun `test offset time serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.OffsetTimeSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.OffsetTimeSerializer(factory2))
val obj = OffsetTime.now()
serdes(obj, factory, factory2)
}
@Test
fun `test offset date time serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.OffsetDateTimeSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.OffsetDateTimeSerializer(factory2))
val obj = OffsetDateTime.now()
serdes(obj, factory, factory2)
}
@Test
fun `test year serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.YearSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.YearSerializer(factory2))
val obj = Year.now()
serdes(obj, factory, factory2)
}
@Test
fun `test year month serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.YearMonthSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.YearMonthSerializer(factory2))
val obj = YearMonth.now()
serdes(obj, factory, factory2)
}
@Test
fun `test month day serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(factory2))
val obj = MonthDay.now()
serdes(obj, factory, factory2)
}
@Test
fun `test period serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(factory2))
val obj = Period.of(99, 98, 97)
serdes(obj, factory, factory2)
}
// TODO: ignored due to Proton-J bug https://issues.apache.org/jira/browse/PROTON-1551
@Ignore
@Test
fun `test certificate holder serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
val obj = BOB_IDENTITY.certificate
serdes(obj, factory, factory2)
}
// TODO: ignored due to Proton-J bug https://issues.apache.org/jira/browse/PROTON-1551
@Ignore
@Test
fun `test party and certificate serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory2))
val obj = BOB_IDENTITY
serdes(obj, factory, factory2)
}
class OtherGeneric<T : Any>
open class GenericSuperclass<T : Any>(val param: OtherGeneric<T>)
class GenericSubclass(param: OtherGeneric<String>) : GenericSuperclass<String>(param) {
override fun equals(other: Any?): Boolean = other is GenericSubclass // This is a bit lame but we just want to check it doesn't throw exceptions
}
@Test
fun `test generic in constructor serialize`() {
val obj = GenericSubclass(OtherGeneric<String>())
serdes(obj)
}
@Test
fun `test StateRef serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val obj = StateRef(SecureHash.randomSHA256(), 0)
serdes(obj, factory, factory2)

View File

@ -1,11 +1,8 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.nodeapi.internal.serialization.amqp.*
import net.corda.nodeapi.internal.serialization.amqp.Field
import net.corda.nodeapi.internal.serialization.amqp.Schema
import net.corda.nodeapi.internal.serialization.amqp.TypeNotation
import net.corda.nodeapi.internal.serialization.amqp.CompositeType
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
fun mangleName(name: String) = "${name}__carpenter"
@ -37,7 +34,7 @@ fun Schema.mangleNames(names: List<String>): Schema {
}
open class AmqpCarpenterBase {
var factory = SerializerFactory()
var factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
fun testName(): String = Thread.currentThread().stackTrace[2].methodName

View File

@ -63,7 +63,7 @@ class CompositeMembers : AmqpCarpenterBase() {
assertEquals("b", amqpSchemaB.fields[1].name)
assertEquals("int", amqpSchemaB.fields[1].type)
val metaSchema = obj.envelope.schema.carpenterSchema()
val metaSchema = obj.envelope.schema.carpenterSchema(ClassLoader.getSystemClassLoader())
// if we know all the classes there is nothing to really achieve here
assert(metaSchema.carpenterSchemas.isEmpty())
@ -92,7 +92,7 @@ class CompositeMembers : AmqpCarpenterBase() {
assert(obj.obj is B)
amqpSchema.carpenterSchema()
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
}
@Test
@ -112,7 +112,7 @@ class CompositeMembers : AmqpCarpenterBase() {
assert(obj.obj is B)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B")))
val carpenterSchema = amqpSchema.carpenterSchema()
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
assertEquals(1, carpenterSchema.size)
@ -140,7 +140,7 @@ class CompositeMembers : AmqpCarpenterBase() {
assert(obj.obj is B)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
val carpenterSchema = amqpSchema.carpenterSchema()
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// just verify we're in the expected initial state, A is carpentable, B is not because
// it depends on A and the dependency chains are in place
@ -200,7 +200,7 @@ class CompositeMembers : AmqpCarpenterBase() {
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
amqpSchema.carpenterSchema()
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
}
@Test(expected = UncarpentableException::class)
@ -226,7 +226,7 @@ class CompositeMembers : AmqpCarpenterBase() {
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
amqpSchema.carpenterSchema()
amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
}
@Suppress("UNUSED")
@ -251,7 +251,7 @@ class CompositeMembers : AmqpCarpenterBase() {
assert(obj.obj is C)
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
TestMetaCarpenter(carpenterSchema.carpenterSchema())
TestMetaCarpenter(carpenterSchema.carpenterSchema(ClassLoader.getSystemClassLoader()))
}
/*

View File

@ -45,14 +45,14 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertTrue(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(2, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// since we're using an envelope generated by seilaising classes defined locally
// it's extremely unlikely we'd need to carpent any classes
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val l2 = mangleSchema.carpenterSchema()
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
assertEquals(1, l2.size)
val aSchema = l2.carpenterSchemas.find { it.name == mangleName(classTestName("A")) }
@ -88,13 +88,13 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals(2, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val aName = mangleName(classTestName("A"))
val l2 = mangleSchema.carpenterSchema()
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
assertEquals(1, l2.size)
@ -132,7 +132,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals(3, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// since we're using an envelope generated by serialising classes defined locally
// it's extremely unlikely we'd need to carpent any classes
@ -141,7 +141,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
// pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus
// needs some carpentry
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val l2 = mangleSchema.carpenterSchema()
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
val aName = mangleName(classTestName("A"))
assertEquals(1, l2.size)
@ -180,14 +180,14 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals(3, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// since we're using an envelope generated by serialising classes defined locally
// it's extremely unlikely we'd need to carpent any classes
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val l2 = mangleSchema.carpenterSchema()
val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
val aName = mangleName(classTestName("A"))
assertEquals(1, l2.size)
@ -235,7 +235,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals(4, serSchema.types.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"), classTestName("B")))
val cSchema = mangleSchema.carpenterSchema()
val cSchema = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
val aName = mangleName(classTestName("A"))
val bName = mangleName(classTestName("B"))
@ -292,7 +292,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
// ignore the return as we expect this to throw
serSchema.mangleNames(listOf(
classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema()
classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema(ClassLoader.getSystemClassLoader())
}
@Test
@ -315,7 +315,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
val amqpSchema = serSchema.mangleNames(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I"))
val aName = mangleName(classTestName("A"))
val iName = mangleName("${this.javaClass.`package`.name}.I")
val carpenterSchema = amqpSchema.carpenterSchema()
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// whilst there are two unknown classes within the envelope A depends on I so we can't construct a
// schema for A until we have for I
@ -362,7 +362,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
val aName = mangleName(classTestName("A"))
val iName = mangleName("${this.javaClass.`package`.name}.I")
val iiName = mangleName("${this.javaClass.`package`.name}.II")
val carpenterSchema = amqpSchema.carpenterSchema()
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// there is nothing preventing us from carpenting up the two interfaces so
// our initial list should contain both interface with A being dependent on both
@ -414,7 +414,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
val aName = mangleName(classTestName("A"))
val iName = mangleName("${this.javaClass.`package`.name}.I")
val iiiName = mangleName("${this.javaClass.`package`.name}.III")
val carpenterSchema = amqpSchema.carpenterSchema()
val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader())
// Since A depends on III and III extends I we will have to construct them
// in that reverse order (I -> III -> A)

View File

@ -36,7 +36,10 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("int", amqpSchema.fields[1].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
assertEquals(1, carpenterSchema.size)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
@ -77,7 +80,10 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("string", amqpSchema.fields[1].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
assertEquals(1, carpenterSchema.size)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }

View File

@ -30,7 +30,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("int", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
@ -58,7 +61,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
@ -90,7 +96,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("long", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
@ -122,7 +131,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("short", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
@ -154,7 +166,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("double", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
@ -186,7 +201,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
assertEquals("float", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
amqpSchema.carpenterSchema(
classloader = ClassLoader.getSystemClassLoader(),
carpenterSchemas = carpenterSchema,
force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)