mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +00:00
Merge open master
This commit is contained in:
@ -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}"
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
@ -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,
|
||||
|
@ -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)
|
@ -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())
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
@ -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<*> {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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]}")
|
||||
|
@ -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) })
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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())
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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")}>" })
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()))
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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)
|
||||
|
@ -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") }
|
||||
|
@ -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)
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user