Merge remote-tracking branch 'open/master' into shams-os-merge-120118

# Conflicts:
#	core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt
#	node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt
#	node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
#	node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealStateSchemaV1.kt
This commit is contained in:
Shams Asari
2018-01-12 08:06:44 +00:00
71 changed files with 1257 additions and 503 deletions

View File

@ -34,13 +34,8 @@ object DevIdentityGenerator {
override val trustStorePassword get() = throw NotImplementedError("Not expected to be called")
}
// TODO The passwords for the dev key stores are spread everywhere and should be constants in a single location
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
nodeSslConfig.certificatesDirectory.createDirectories()
nodeSslConfig.createDevKeyStores(rootCert, intermediateCa, legalName)
nodeSslConfig.createDevKeyStores(legalName)
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
@ -54,16 +49,12 @@ object DevIdentityGenerator {
val keyPairs = (1..dirs.size).map { generateKeyPair() }
val compositeKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
keyPairs.zip(dirs) { keyPair, nodeDir ->
val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey ->
X509Utilities.createCertificate(
CertificateType.SERVICE_IDENTITY,
intermediateCa.certificate,
intermediateCa.keyPair,
DEV_INTERMEDIATE_CA.certificate,
DEV_INTERMEDIATE_CA.keyPair,
notaryName.x500Principal,
publicKey)
}
@ -74,7 +65,7 @@ object DevIdentityGenerator {
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
keyPair.private,
"cordacadevkeypass".toCharArray(),
arrayOf(serviceKeyCert, intermediateCa.certificate, rootCert))
arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
keystore.save(distServKeyStoreFile, "cordacadevpass")
}

View File

@ -9,12 +9,17 @@ import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal
// TODO Merge this file and DevIdentityGenerator
/**
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) {
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
@ -39,6 +44,17 @@ fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateC
}
}
fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair()
val cert = X509Utilities.createCertificate(
CertificateType.NETWORK_MAP,
rootCa.certificate,
rootCa.keyPair,
X500Principal("CN=Network Map,O=R3 Ltd,L=London,C=GB"),
keyPair.public)
return CertificateAndKeyPair(cert, keyPair)
}
/**
* Create a dev node CA cert, as a sub-cert of the given [intermediateCa], and matching key pair using the given
* [CordaX500Name] as the cert subject.
@ -55,3 +71,16 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500N
nameConstraints = nameConstraints)
return CertificateAndKeyPair(cert, keyPair)
}
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_INTERMEDIATE_CA)
val DEV_ROOT_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_ROOT_CA)
// We need a class so that we can get hold of the class loader
internal object DevCaHelper {
fun loadDevCa(alias: String): CertificateAndKeyPair {
// TODO: Should be identity scheme
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
return caKeyStore.getCertificateAndKeyPair(alias, "cordacadevkeypass")
}
}

View File

@ -13,12 +13,14 @@ import java.security.SignatureException
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
* to be in the same order as the identities.
*/
// TODO Move this to net.corda.nodeapi.internal.network
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
// public keys.
@CordaSerializable
class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<DigitalSignature>) {
// TODO Add root cert param (or TrustAnchor) to make sure all the identities belong to the same root
fun verified(): NodeInfo {
val nodeInfo = raw.deserialize()
val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }

View File

@ -1,16 +1,12 @@
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.verify
import net.corda.core.identity.Party
import net.corda.core.internal.CertRole
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.crypto.X509Utilities
import java.security.SignatureException
import java.security.cert.CertPathValidatorException
import java.security.cert.X509Certificate
import java.time.Instant
@ -55,30 +51,8 @@ data class NetworkParameters(
@CordaSerializable
data class NotaryInfo(val identity: Party, val validating: Boolean)
/**
* A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data
* contained within.
*/
@CordaSerializable
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val signature: DigitalSignatureWithCert) {
/**
* Return the deserialized NetworkMap if the signature and certificate can be verified.
*
* @throws CertPathValidatorException if the certificate path is invalid.
* @throws SignatureException if the signature is invalid.
*/
@Throws(SignatureException::class, CertPathValidatorException::class)
fun verified(trustedRoot: X509Certificate?): NetworkMap {
signature.by.publicKey.verify(raw.bytes, signature)
if (trustedRoot != null) {
// Assume network map cert is under the default trust root.
X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot)
}
return raw.deserialize()
}
}
// TODO: This class should reside in the [DigitalSignature] class.
// TODO: Removing the val from signatureBytes causes serialisation issues
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)
return verified()
}

View File

@ -1,35 +1,32 @@
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.internal.signWithCert
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.security.KeyPair
class NetworkParametersCopier(
networkParameters: NetworkParameters,
signingKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
overwriteFile: Boolean = false
) {
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
private val serializedNetworkParameters = networkParameters.let {
val serialize = it.serialize()
val signature = signingKeyPair.sign(serialize)
SignedData(serialize, signature).serialize()
}
private val serialisedSignedNetParams = networkParameters.signWithCert(
networkMapCa.keyPair.private,
networkMapCa.certificate
).serialize()
fun install(nodeDir: Path) {
try {
serializedNetworkParameters.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
serialisedSignedNetParams.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
} catch (e: FileAlreadyExistsException) {
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
// ignore this exception as we're happy with the existing file.
}
}
}
}

View File

@ -54,7 +54,6 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>): T =
deserialize(bytes, T::class.java)
@Throws(NotSerializableException::class)
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
deserializeAndReturnEnvelope(bytes, T::class.java)

View File

@ -106,7 +106,7 @@ class EnumEvolutionSerializer(
// to the name as it exists. We want to test any new constants have been added to the end
// of the enum class
val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices
.associateBy ({ it.value.toInt() }, { conversions[it.name] }))
.associateBy({ it.value.toInt() }, { conversions[it.name] }))
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")
@ -133,4 +133,4 @@ class EnumEvolutionSerializer(
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
}
}

View File

@ -96,7 +96,7 @@ class EvolutionSerializer(
old.fields.forEach {
val returnType = it.getTypeAsClass(factory.classloader)
oldArgs[it.name] = OldParam(
returnType, idx++, PropertySerializer.make(it.name, null, returnType, factory))
returnType, idx++, PropertySerializer.make(it.name, PublicPropertyReader(null), returnType, factory))
}
val readers = constructor.parameters.map {
@ -130,3 +130,34 @@ class EvolutionSerializer(
}
}
/**
* Instances of this type are injected into a [SerializerFactory] at creation time to dictate the
* behaviour of evolution within that factory. Under normal circumstances this will simply
* be an object that returns an [EvolutionSerializer]. Of course, any implementation that
* extends this class can be written to invoke whatever behaviour is desired.
*/
abstract class EvolutionSerializerGetterBase {
abstract fun getEvolutionSerializer(
factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any>
}
/**
* The normal use case for generating an [EvolutionSerializer]'s based on the differences
* between the received schema and the class as it exists now on the class path,
*/
class EvolutionSerializerGetter : EvolutionSerializerGetterBase() {
override fun getEvolutionSerializer(factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> =
factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory)
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas)
}
}
}

View File

@ -26,6 +26,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
propertiesForSerialization(kotlinConstructor, clazz, factory)
}
fun getPropertySerializers() = propertySerializers
private val typeName = nameForType(clazz)
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")

View File

@ -1,17 +1,74 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Method
import java.lang.reflect.Type
import java.lang.reflect.Field
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.kotlinProperty
abstract class PropertyReader {
abstract fun read(obj: Any?): Any?
abstract fun isNullable(): Boolean
}
class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
init {
readMethod?.isAccessible = true
}
private fun Method.returnsNullable(): Boolean {
try {
val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { it.javaGetter == this }?.returnType?.toString() ?: "?"
return returnTypeString.endsWith('?') || returnTypeString.endsWith('!')
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
// TODO: Revisit this when Kotlin issue is fixed.
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
return true
}
}
override fun read(obj: Any?): Any? {
return readMethod!!.invoke(obj)
}
override fun isNullable(): Boolean = readMethod?.returnsNullable() ?: false
}
class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader() {
init {
loggerFor<PropertySerializer>().warn("Create property Serializer for private property '${field.name}' not "
+ "exposed by a getter on class '$parentType'\n"
+ "\tNOTE: This behaviour will be deprecated at some point in the future and a getter required")
}
override fun read(obj: Any?): Any? {
field.isAccessible = true
val rtn = field.get(obj)
field.isAccessible = false
return rtn
}
override fun isNullable() = try {
field.kotlinProperty?.returnType?.isMarkedNullable ?: false
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
// TODO: Revisit this when Kotlin issue is fixed.
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
true
}
}
/**
* 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 propertyReader: PropertyReader, val resolvedType: Type) {
abstract fun writeClassInfo(output: SerializationOutput)
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
@ -44,25 +101,11 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
}
private fun generateMandatory(): Boolean {
return isJVMPrimitive || readMethod?.returnsNullable() == false
}
private fun Method.returnsNullable(): Boolean {
try {
val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { it.javaGetter == this }?.returnType?.toString() ?: "?"
return returnTypeString.endsWith('?') || returnTypeString.endsWith('!')
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
// TODO: Revisit this when Kotlin issue is fixed.
logger.error("Unexpected internal Kotlin error", e)
return true
}
return isJVMPrimitive || !(propertyReader.isNullable())
}
companion object {
private val logger = contextLogger()
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
readMethod?.isAccessible = true
fun make(name: String, readMethod: PropertyReader, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
if (SerializerFactory.isPrimitive(resolvedType)) {
return when (resolvedType) {
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
@ -78,7 +121,8 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
* A property serializer for a complex type (another object).
*/
class DescribedTypePropertySerializer(
name: String, readMethod: Method?,
name: String,
readMethod: PropertyReader,
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.
@ -90,12 +134,15 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
}
}
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
override fun readProperty(
obj: Any?,
schemas: SerializationSchemas,
input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
input.readObjectOrNull(obj, schemas, resolvedType)
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
output.writeObjectOrNull(readMethod!!.invoke(obj), data, resolvedType)
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType)
}
private val nameForDebug = "$name(${resolvedType.typeName})"
@ -104,7 +151,10 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
/**
* 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: PropertyReader,
resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
override fun writeClassInfo(output: SerializationOutput) {}
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
@ -112,7 +162,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
val value = readMethod!!.invoke(obj)
val value = propertyReader.read(obj)
if (value is ByteArray) {
data.putObject(Binary(value))
} else {
@ -126,7 +176,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
* 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: PropertyReader) :
PropertySerializer(name, readMethod, Character::class.java) {
override fun writeClassInfo(output: SerializationOutput) {}
@ -135,7 +185,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
val input = readMethod!!.invoke(obj)
val input = propertyReader.read(obj)
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
}
}

View File

@ -346,19 +346,54 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact
}
}
// This method concatentates various elements of the types recursively as unencoded strings into the hasher, effectively
// This method concatenates 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 {
return if (type in alreadySeen) {
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher {
// We don't include Example<?> and Example<T> where type is ? or T in this otherwise we
// generate different fingerprints for class Outer<T>(val a: Inner<T>) when serialising
// and deserializing (assuming deserialization is occurring in a factory that didn't
// serialise the object in the first place (and thus the cache lookup fails). This is also
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
return if (type in alreadySeen && (type !is SerializerFactory.AnyType) && (type !is TypeVariable<*>)) {
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
} else {
alreadySeen += type
try {
when (type) {
is SerializerFactory.AnyType -> hasher.putUnencodedChars(ANY_TYPE_HASH)
is ParameterizedType -> {
// Hash the rawType + params
val clazz = type.rawType as Class<*>
val startingHash = if (isCollectionOrMap(clazz)) {
hasher.putUnencodedChars(clazz.name)
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
}
}
// ... and concatenate the type data for each parameter type.
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4)
}
}
// Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when
// looking at A and B from Example<A, B> (remember we call this function recursively). When
// serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but
// when deserializing we only have the wildcard placeholder ?, or AnyType
//
// Note, TypeVariable<*> used to be encoded as TYPE_VARIABLE_HASH but that again produces a
// differing fingerprint on serialisation and deserialization
is SerializerFactory.AnyType,
is TypeVariable<*> -> {
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
}
is Class<*> -> {
if (type.isArray) {
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory).putUnencodedChars(ARRAY_HASH)
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4)
.putUnencodedChars(ARRAY_HASH)
} else if (SerializerFactory.isPrimitive(type)) {
hasher.putUnencodedChars(type.name)
} else if (isCollectionOrMap(type)) {
@ -377,32 +412,18 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
// to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)
} else {
fingerprintForObject(type, type, alreadySeen, hasher, factory)
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
}
}
}
}
is ParameterizedType -> {
// Hash the rawType + params
val clazz = type.rawType as Class<*>
val startingHash = if (isCollectionOrMap(clazz)) {
hasher.putUnencodedChars(clazz.name)
} else {
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)
}
}
// Hash the element type + some array hash
is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen,
hasher, factory).putUnencodedChars(ARRAY_HASH)
hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH)
// TODO: include bounds
is TypeVariable<*> -> hasher.putUnencodedChars(type.name).putUnencodedChars(TYPE_VARIABLE_HASH)
is WildcardType -> hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
is WildcardType -> {
hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
}
else -> throw NotSerializableException("Don't know how to hash")
}
} catch (e: NotSerializableException) {
@ -416,15 +437,21 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) &&
!EnumSet::class.java.isAssignableFrom(type)
private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
private fun fingerprintForObject(
type: Type,
contextType: Type?,
alreadySeen: MutableSet<Type>,
hasher: Hasher,
factory: SerializerFactory,
offset: Int = 0): Hasher {
// Hash the class + properties + interfaces
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory)
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, offset+4)
.putUnencodedChars(prop.name)
.putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) }
return hasher
}

View File

@ -20,6 +20,7 @@ import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.kotlinProperty
/**
* Annotation indicating a constructor to be used to reconstruct instances of a class during deserialization.
@ -29,8 +30,8 @@ import kotlin.reflect.jvm.javaType
annotation class ConstructorForDeserialization
data class ConstructorDestructorMethods(
val getters : Collection<PropertySerializer>,
val setters : Collection<Method?>)
val getters: Collection<PropertySerializer>,
val setters: Collection<Method?>)
/**
* Code for finding the constructor we will use for deserialization.
@ -100,26 +101,31 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
for (param in kotlinConstructor.parameters) {
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
val matchingProperty = properties[name] ?:
try {
clazz.getDeclaredField(param.name)
throw NotSerializableException("Property '$name' or its getter is non public, this renders class '$clazz' unserializable")
} catch (e: NoSuchFieldException) {
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. " +
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
}
if (name in properties) {
val matchingProperty = properties[name]!!
// 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." +
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
val returnType = resolveTypeVariables(getter.genericReturnType, type)
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
rc += PropertySerializer.make(name, getter, returnType, factory)
// 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." +
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
val returnType = resolveTypeVariables(getter.genericReturnType, type)
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
rc += PropertySerializer.make(name, PublicPropertyReader(getter), returnType, factory)
} else {
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
}
} else {
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
try {
val field = (clazz.getDeclaredField(param.name))
rc += PropertySerializer.make(name, PrivatePropertyReader(field, type), field.genericType, factory)
} catch (e: NoSuchFieldException) {
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. " +
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
}
}
}
return ConstructorDestructorMethods(rc, emptyList())
@ -130,11 +136,11 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
* and use those
*/
private fun propertiesForSerializationFromSetters(
properties : Map<String, PropertyDescriptor>,
properties: Map<String, PropertyDescriptor>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
val getters : MutableList<PropertySerializer> = ArrayList(properties.size)
val setters : MutableList<Method?> = ArrayList(properties.size)
val getters: MutableList<PropertySerializer> = ArrayList(properties.size)
val setters: MutableList<Method?> = ArrayList(properties.size)
properties.forEach { property ->
val getter: Method? = property.value.readMethod
@ -146,8 +152,8 @@ private fun propertiesForSerializationFromSetters(
// the getter / setter vs property as if there is a difference then that property isn't reported
// by the BEAN inspector and thus we don't consider that case here
getters += PropertySerializer.make(property.key, getter, resolveTypeVariables(getter.genericReturnType, type),
factory)
getters += PropertySerializer.make(property.key, PublicPropertyReader(getter),
resolveTypeVariables(getter.genericReturnType, type), factory)
setters += setter
}
@ -159,15 +165,22 @@ private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawG
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
}
private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): ConstructorDestructorMethods {
private fun propertiesForSerializationFromAbstract(
clazz: Class<*>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
// 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 }.filterNot { it is IndexedPropertyDescriptor }
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.
val getter = property.readMethod ?: throw NotSerializableException("Property has no getter method for ${property.name} of $clazz.")
val getter = property.readMethod ?: throw NotSerializableException(
"Property has no getter method for ${property.name} of $clazz.")
val returnType = resolveTypeVariables(getter.genericReturnType, type)
rc += PropertySerializer.make(property.name, getter, returnType, factory)
rc += PropertySerializer.make(property.name, PublicPropertyReader(getter), returnType, factory)
}
return ConstructorDestructorMethods(rc, emptyList())
}

View File

@ -20,6 +20,10 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
/**
* Factory of serializers designed to be shared across threads and invocations.
*
* @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal
* use case is an [EvolutionSerializer] type is returned. However, in some scenarios, primarily testing, this
* can be altered to fit the requirements of the test.
*/
// TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency
// TODO: maybe support for caching of serialized form of some core types for performance
@ -33,27 +37,27 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
// 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
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
open class SerializerFactory(
val whitelist: ClassWhitelist,
cl: ClassLoader,
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter()) {
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
private val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
open val classCarpenter = ClassCarpenter(cl, whitelist)
val classloader: ClassLoader
get() = classCarpenter.classloader
private fun getEvolutionSerializer(
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> {
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, this, schemas)
}
}
}
private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas)
= evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
fun getSerializersByDescriptor() = serializersByDescriptor
fun getTransformsCache() = transformsCache
/**
* Look up, and manufacture if necessary, a serializer for the given type.
@ -92,7 +96,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
whitelist.requireWhitelisted(actualType)
EnumSerializer(actualType, actualClass ?: declaredClass, this)
}
else -> makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
else -> {
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
}
}
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
@ -101,23 +107,23 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
}
/**
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
* type.
* Try and infer concrete types for any generics type variables for the actual class encountered,
* based on the declared type.
*/
// TODO: test GenericArrayType
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, declaredType: Type): Type? =
when (declaredType) {
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
// Nothing to infer, otherwise we'd have ParameterizedType
is Class<*> -> actualClass
is GenericArrayType -> {
val declaredComponent = declaredType.genericComponentType
inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray()
}
is TypeVariable<*> -> actualClass
is WildcardType -> actualClass
else -> null
}
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>,
declaredType: Type) : Type? = when (declaredType) {
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
// Nothing to infer, otherwise we'd have ParameterizedType
is Class<*> -> actualClass
is GenericArrayType -> {
val declaredComponent = declaredType.genericComponentType
inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray()
}
is TypeVariable<*> -> actualClass
is WildcardType -> actualClass
else -> null
}
/**
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
@ -213,9 +219,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
try {
val serialiser = processSchemaEntry(typeNotation)
// if we just successfully built a serialiser for the type but the type fingerprint
// if we just successfully built a serializer 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
// instance of the class, as such we need to build an EvolutionSerializer
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
}
@ -336,7 +342,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
"${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}"
} else type.name
}
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
is ParameterizedType -> {
"${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
}
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
is WildcardType -> "?"
is TypeVariable<*> -> "?"
@ -381,3 +389,4 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
override fun toString(): String = "?"
}
}

View File

@ -200,7 +200,7 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
* @param sf the [SerializerFactory] building this transform set. Needed as each can define it's own
* class loader and this dictates which classes we can and cannot see
*/
fun get(name: String, sf: SerializerFactory) = sf.transformsCache.computeIfAbsent(name) {
fun get(name: String, sf: SerializerFactory) = sf.getTransformsCache().computeIfAbsent(name) {
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
try {
val clazz = sf.classloader.loadClass(name)

View File

@ -25,7 +25,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
val constructor = constructorForDeserialization(obj.javaClass)
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
for (prop in props.getters) {
extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
extraProperties[prop.name] = prop.propertyReader.read(obj)
}
} catch (e: NotSerializableException) {
logger.warn("Unexpected exception", e)

View File

@ -2,10 +2,12 @@ package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import org.assertj.core.api.Assertions;
import org.junit.Ignore;
import org.junit.Test;
import java.io.NotSerializableException;
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
public class ErrorMessageTests {
private String errMsg(String property, String testname) {
return "Property '"
@ -29,7 +31,12 @@ public class ErrorMessageTests {
@Test
public void testJavaConstructorAnnotations() {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1)))

View File

@ -0,0 +1,93 @@
package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.core.serialization.SerializedBytes;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import org.junit.Test;
import java.io.NotSerializableException;
import static org.jgroups.util.Util.assertEquals;
public class JavaGenericsTest {
private static class Inner {
private final Integer v;
private Inner(Integer v) { this.v = v; }
public Integer getV() { return v; }
}
private static class A<T> {
private final T t;
private A(T t) { this.t = t; }
public T getT() { return t; }
}
@Test
public void basicGeneric() throws NotSerializableException {
A a1 = new A(1);
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
SerializationOutput ser = new SerializationOutput(factory);
SerializedBytes<?> bytes = ser.serialize(a1);
DeserializationInput des = new DeserializationInput(factory);
A a2 = des.deserialize(bytes, A.class);
assertEquals(1, a2.getT());
}
private SerializedBytes<?> forceWildcardSerialize(A<?> a) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
return (new SerializationOutput(factory)).serialize(a);
}
private SerializedBytes<?> forceWildcardSerializeFactory(
A<?> a,
SerializerFactory factory) throws NotSerializableException {
return (new SerializationOutput(factory)).serialize(a);
}
private A<?> forceWildcardDeserialize(SerializedBytes<?> bytes) throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
DeserializationInput des = new DeserializationInput(factory);
return des.deserialize(bytes, A.class);
}
private A<?> forceWildcardDeserializeFactory(
SerializedBytes<?> bytes,
SerializerFactory factory) throws NotSerializableException {
return (new DeserializationInput(factory)).deserialize(bytes, A.class);
}
@Test
public void forceWildcard() throws NotSerializableException {
SerializedBytes<?> bytes = forceWildcardSerialize(new A(new Inner(29)));
Inner i = (Inner)forceWildcardDeserialize(bytes).getT();
assertEquals(29, i.getV());
}
@Test
public void forceWildcardSharedFactory() throws NotSerializableException {
SerializerFactory factory = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
new EvolutionSerializerGetter());
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory);
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();
assertEquals(29, i.getV());
}
}

View File

@ -0,0 +1,83 @@
package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.NotSerializableException;
import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentHashMap;
public class JavaPrivatePropertyTests {
static class C {
private String a;
C(String a) { this.a = a; }
}
static class C2 {
private String a;
C2(String a) { this.a = a; }
public String getA() { return a; }
}
@Test
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerializerGetter);
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
C c = new C("dripping taps");
C c2 = des.deserialize(ser.serialize(c), C.class);
assertEquals (c.a, c2.a);
//
// Now ensure we actually got a private property serializer
//
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
f.setAccessible(true);
ConcurrentHashMap<Object, AMQPSerializer<Object>> serializersByDescriptor =
(ConcurrentHashMap<Object, AMQPSerializer<Object>>) f.get(factory);
assertEquals(1, serializersByDescriptor.size());
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
assertEquals(1, cSerializer.getPropertySerializers().component1().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().component1().toArray();
assertTrue (((PropertySerializer)propertyReaders[0]).getPropertyReader() instanceof PrivatePropertyReader);
}
@Test
public void singlePrivateWithConstructorAndGetter()
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(), evolutionSerializerGetter);
SerializationOutput ser = new SerializationOutput(factory);
DeserializationInput des = new DeserializationInput(factory);
C2 c = new C2("dripping taps");
C2 c2 = des.deserialize(ser.serialize(c), C2.class);
assertEquals (c.a, c2.a);
//
// Now ensure we actually got a private property serializer
//
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
f.setAccessible(true);
ConcurrentHashMap<Object, AMQPSerializer<Object>> serializersByDescriptor =
(ConcurrentHashMap<Object, AMQPSerializer<Object>>) f.get(factory);
assertEquals(1, serializersByDescriptor.size());
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
assertEquals(1, cSerializer.getPropertySerializers().component1().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().component1().toArray();
assertTrue (((PropertySerializer)propertyReaders[0]).getPropertyReader() instanceof PublicPropertyReader);
}
}

View File

@ -29,7 +29,9 @@ public class JavaSerialiseEnumTests {
public void testJavaConstructorAnnotations() throws NotSerializableException {
Bra bra = new Bra(Bras.UNDERWIRE);
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(bra);
}

View File

@ -172,8 +172,11 @@ public class JavaSerializationOutputTests {
}
private Object serdes(Object obj) throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(obj);

View File

@ -125,7 +125,9 @@ public class ListsSerializationJavaTest {
// Have to have own version as Kotlin inline functions cannot be easily called from Java
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(container);
DeserializationInput des = new DeserializationInput(factory1);

View File

@ -109,7 +109,12 @@ public class SetterConstructorTests {
// despite having no constructor we should still be able to serialise an instance of C
@Test
public void serialiseC() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
SerializationOutput ser = new SerializationOutput(factory1);
C c1 = new C();
@ -178,7 +183,11 @@ public class SetterConstructorTests {
@Test
public void deserialiseC() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
C cPre1 = new C();
@ -241,7 +250,11 @@ public class SetterConstructorTests {
@Test
public void serialiseOuterAndInner() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
Inner1 i1 = new Inner1("Hello");
Inner2 i2 = new Inner2();
@ -263,7 +276,11 @@ public class SetterConstructorTests {
@Test
public void typeMistmatch() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
TypeMismatch tm = new TypeMismatch();
tm.setA(10);
@ -279,7 +296,11 @@ public class SetterConstructorTests {
@Test
public void typeMistmatch2() throws NotSerializableException {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
SerializerFactory factory1 = new SerializerFactory(
AllWhitelist.INSTANCE,
ClassLoader.getSystemClassLoader(),
evolutionSerialiserGetter);
TypeMismatch2 tm = new TypeMismatch2();
tm.setA("10");

View File

@ -168,7 +168,7 @@ class X509UtilitiesTest {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa)
// Load back server certificate
val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
@ -203,7 +203,7 @@ class X509UtilitiesTest {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa)
sslConfig.createTrustStore(rootCa.certificate)
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)

View File

@ -1,12 +1,12 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializedBytes
import org.apache.qpid.proton.codec.Data
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import java.io.NotSerializableException
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting())
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
class TestSerializationOutput(

View File

@ -11,13 +11,14 @@ class DeserializeAndReturnEnvelopeTests {
@Suppress("NOTHING_TO_INLINE")
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
val factory = testDefaultFactoryNoEvolution()
@Test
fun oneType() {
data class A(val a: Int, val b: String)
val a = A(10, "20")
val factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
@ -33,7 +34,6 @@ class DeserializeAndReturnEnvelopeTests {
val b = B(A(10, "20"), 30.0F)
val factory = testDefaultFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))

View File

@ -12,7 +12,7 @@ class DeserializeMapTests {
private const val VERBOSE = false
}
private val sf = testDefaultFactory()
private val sf = testDefaultFactoryNoEvolution()
@Test
fun mapTest() {

View File

@ -18,7 +18,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
//
// Setup the test
//
val setupFactory = testDefaultFactory()
val setupFactory = testDefaultFactoryNoEvolution()
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
@ -57,7 +57,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
//
// Setup the test
//
val setupFactory = testDefaultFactory()
val setupFactory = testDefaultFactoryNoEvolution()
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })

View File

@ -17,8 +17,8 @@ class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase(AllWhitelis
private const val VERBOSE = false
}
private val sf = testDefaultFactory()
private val sf2 = testDefaultFactory()
private val sf = testDefaultFactoryNoEvolution()
private val sf2 = testDefaultFactoryNoEvolution()
@Test
fun singleInt() {

View File

@ -27,7 +27,7 @@ class DeserializeNeedingCarpentryTests : AmqpCarpenterBase(AllWhitelist) {
private const val VERBOSE = false
}
private val sf1 = testDefaultFactory()
private val sf1 = testDefaultFactoryNoEvolution()
// Deserialize with whitelisting on to check that `CordaSerializable` annotation present.
private val sf2 = testDefaultFactoryWithWhitelist()

View File

@ -16,8 +16,8 @@ class DeserializeSimpleTypesTests {
private const val VERBOSE = false
}
val sf1 = testDefaultFactory()
val sf2 = testDefaultFactory()
val sf1 = testDefaultFactoryNoEvolution()
val sf2 = testDefaultFactoryNoEvolution()
@Test
fun testChar() {

View File

@ -6,6 +6,8 @@ import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -387,21 +389,25 @@ class EnumEvolvabilityTests {
data class C1(val annotatedEnum: AnnotatedEnumOnce)
val sf = testDefaultFactory()
val f = sf.javaClass.getDeclaredField("transformsCache")
f.isAccessible = true
assertEquals(0, sf.transformsCache.size)
val transformsCache = f.get(sf) as ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>
assertEquals(0, transformsCache.size)
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
assertEquals(2, sf.transformsCache.size)
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals(2, transformsCache.size)
assertTrue(transformsCache.containsKey(C1::class.java.name))
assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
assertEquals(3, sf.transformsCache.size)
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
assertTrue(sf.transformsCache.containsKey(C2::class.java.name))
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals(3, transformsCache.size)
assertTrue(transformsCache.containsKey(C1::class.java.name))
assertTrue(transformsCache.containsKey(C2::class.java.name))
assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
assertEquals(sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])

View File

@ -65,7 +65,7 @@ class EnumTests {
@Suppress("NOTHING_TO_INLINE")
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
private val sf1 = testDefaultFactory()
private val sf1 = testDefaultFactoryNoEvolution()
@Test
fun serialiseSimpleTest() {

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.assertj.core.api.Assertions
import org.junit.Ignore
import org.junit.Test
import java.io.NotSerializableException
@ -12,6 +13,8 @@ class ErrorMessagesTests {
private fun errMsg(property:String, testname: String) =
"Property '$property' or its getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C"
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun privateProperty() {
data class C(private val a: Int)
@ -25,6 +28,8 @@ class ErrorMessagesTests {
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname))
}
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun privateProperty2() {
data class C(val a: Int, private val b: Int)
@ -38,6 +43,8 @@ class ErrorMessagesTests {
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("b", testname))
}
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun privateProperty3() {
// despite b being private, the getter we've added is public and thus allows for the serialisation
@ -54,6 +61,8 @@ class ErrorMessagesTests {
val c = DeserializationInput(sf).deserialize(bytes)
}
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
@Test
fun protectedProperty() {
data class C(protected val a: Int)

View File

@ -0,0 +1,22 @@
package net.corda.nodeapi.internal.serialization.amqp
import java.io.NotSerializableException
/**
* An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a
* [SerializerFactory]. This is most useful in testing where it is known that evolution should not be
* occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This
* prevents that by simply throwing an exception whenever such a serializer is requested.
*/
class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() {
override fun getEvolutionSerializer(factory: SerializerFactory,
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
schemas: SerializationSchemas): AMQPSerializer<Any> {
throw NotSerializableException("No evolution should be occurring\n" +
" ${typeNotation.name}\n" +
" ${typeNotation.descriptor.name}\n" +
" ${newSerializer.type.typeName}\n" +
" ${newSerializer.typeDescriptor}\n\n${schemas.schema}")
}
}

View File

@ -2,26 +2,90 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializedBytes
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.junit.Test
import java.io.File
import java.net.URI
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
class GenericsTests {
companion object {
val VERBOSE = false
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
}
private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit
private fun <T : Any> BytesAndSchemas<T>.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit
private fun ConcurrentHashMap<Any, AMQPSerializer<Any>>.printKeyToType() {
if (!VERBOSE) return
forEach {
println("Key = ${it.key} - ${it.value.type.typeName}")
}
println()
}
@Test
fun twoDifferentTypesSameParameterizedOuter() {
data class G<A>(val a: A)
val factory = testDefaultFactoryNoEvolution()
val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
listOf(factory, testDefaultFactory()).forEach { f ->
DeserializationInput(f).deserialize(bytes1.obj).apply { assertEquals("hi", this.a) }
DeserializationInput(f).deserialize(bytes2.obj).apply { assertEquals(121, this.a) }
}
}
@Test
fun doWeIgnoreMultipleParams() {
data class G1<out T>(val a: T)
data class G2<out T>(val a: T)
data class Wrapper<out T>(val a: Int, val b: G1<T>, val c: G2<T>)
val factory = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactoryNoEvolution()
val bytes = SerializationOutput(factory).serializeAndReturnSchema(Wrapper(1, G1("hi"), G2("poop"))).apply { printSchema() }
printSeparator()
DeserializationInput(factory2).deserialize(bytes.obj)
}
@Test
fun nestedSerializationOfGenerics() {
data class G<T>(val a: T)
data class Wrapper<T>(val a: Int, val b: G<T>)
data class G<out T>(val a: T)
data class Wrapper<out T>(val a: Int, val b: G<T>)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val altContextFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val factory = testDefaultFactoryNoEvolution()
val altContextFactory = testDefaultFactoryNoEvolution()
val ser = SerializationOutput(factory)
val bytes = ser.serializeAndReturnSchema(G("hi"))
val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a)
assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a)
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi")))
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))).apply { printSchema() }
factory.getSerializersByDescriptor().printKeyToType()
printSeparator()
DeserializationInput(factory).deserialize(bytes2.obj).apply {
assertEquals(1, a)
@ -36,7 +100,7 @@ class GenericsTests {
@Test
fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
data class G(val a : Int)
data class G(val a: Int)
data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -71,27 +135,30 @@ class GenericsTests {
ser.serialize(Wrapper(Container(InnerA(1)))).apply {
factories.forEach {
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
}
}
ser.serialize(Wrapper(Container(InnerB(1)))).apply {
factories.forEach {
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
}
}
ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply {
factories.forEach {
DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) }
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
}
}
}
@Test
fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
data class Inner(val a : Int)
data class Inner(val a: Int)
data class Container<T : Any>(val b: Inner)
data class Wrapper<T: Any>(val c: Container<T>)
data class Wrapper<T : Any>(val c: Container<T>)
val factorys = listOf(
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
@ -111,4 +178,103 @@ class GenericsTests {
}
}
}
}
data class ForceWildcard<out T>(val t: T)
private fun forceWildcardSerialize(
a: ForceWildcard<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())): SerializedBytes<*> {
val bytes = SerializationOutput(factory).serializeAndReturnSchema(a)
factory.getSerializersByDescriptor().printKeyToType()
bytes.printSchema()
return bytes.obj
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserializeString(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<String>>)
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserializeDouble(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<Double>>)
}
@Suppress("UNCHECKED_CAST")
private fun forceWildcardDeserialize(
bytes: SerializedBytes<*>,
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<*>>)
}
@Test
fun forceWildcard() {
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello")))
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0)))
}
@Test
fun forceWildcardSharedFactory() {
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"), f), f)
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0), f), f)
}
@Test
fun forceWildcardDeserialize() {
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello")))
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10)))
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0)))
}
@Test
fun forceWildcardDeserializeSharedFactory() {
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"), f), f)
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10), f), f)
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0), f), f)
}
@Test
fun loadGenericFromFile() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
// Uncomment to re-generate test files, needs to be done in three stages
// File(URI("$localPath/$resource")).writeBytes(forceWildcardSerialize(ForceWildcard("wibble")).bytes)
assertEquals("wibble",
DeserializationInput(sf).deserialize(SerializedBytes<ForceWildcard<*>>(
File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t)
}
interface DifferentBounds {
fun go()
}
@Test
fun differentBounds() {
data class A (val a: Int): DifferentBounds {
override fun go() {
println(a)
}
}
data class G<out T : DifferentBounds>(val b: T)
val factorys = listOf(
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
val ser = SerializationOutput(factorys[0])
ser.serialize(G(A(10))).apply {
factorys.forEach {
}
}
}
}

View File

@ -0,0 +1,117 @@
package net.corda.nodeapi.internal.serialization.amqp
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.apache.qpid.proton.amqp.Symbol
import java.util.concurrent.ConcurrentHashMap
class PrivatePropertyTests {
private val factory = testDefaultFactory()
@Test
fun testWithOnePrivateProperty() {
data class C(private val b: String)
val c1 = C("Pants are comfortable sometimes")
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePrivatePropertyNullableNotNull() {
data class C(private val b: String?)
val c1 = C("Pants are comfortable sometimes")
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePrivatePropertyNullableNull() {
data class C(private val b: String?)
val c1 = C(null)
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePublicOnePrivateProperty() {
data class C(val a: Int, private val b: Int)
val c1 = C(1, 2)
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
@Test
fun testWithOnePublicOnePrivateProperty2() {
data class C(val a: Int, private val b: Int)
val c1 = C(1, 2)
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
assertEquals(1, schemaAndBlob.schema.types.size)
val field = SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")
field.isAccessible = true
@Suppress("UNCHECKED_CAST")
val serializersByDescriptor = field.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer)
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.getters.toList()
assertEquals(2, propertySerializers.size)
// a was public so should have a synthesised getter
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
// b is private and thus won't have teh getter so we'll have reverted
// to using reflection to remove the inaccessible property
assertTrue(propertySerializers[1].propertyReader is PrivatePropertyReader)
}
}
@Test
fun testGetterMakesAPublicReader() {
data class C(val a: Int, private val b: Int) {
@Suppress("UNUSED")
fun getB() = b
}
val c1 = C(1, 2)
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
assertEquals(1, schemaAndBlob.schema.types.size)
val field = SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")
field.isAccessible = true
@Suppress("UNCHECKED_CAST")
val serializersByDescriptor = field.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer)
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.getters.toList()
assertEquals(2, propertySerializers.size)
// as before, a is public so we'll use the getter method
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
// the getB() getter explicitly added means we should use the "normal" public
// method reader rather than the private oen
assertTrue(propertySerializers[1].propertyReader is PublicPropertyReader)
}
}
@Test
fun testNested() {
data class Inner(private val a: Int)
data class Outer(private val i: Inner)
val c1 = Outer(Inner(1010101))
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
assertEquals(c1, c2)
}
}

View File

@ -169,9 +169,11 @@ class SerializationOutputTests {
private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting()),
freshDeserializationFactory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting()),
expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): T {
val ser = SerializationOutput(factory)

View File

@ -10,7 +10,7 @@ class SerializeAndReturnSchemaTest {
@Suppress("NOTHING_TO_INLINE")
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
val factory = testDefaultFactory()
val factory = testDefaultFactoryNoEvolution()
// just a simple test to verify the internal test extension for serialize does
// indeed give us the correct schema back. This is more useful in support of other

View File

@ -45,7 +45,7 @@ class StaticInitialisationOfSerializedObjectTest {
}
@Test
fun KotlinObjectWithCompanionObject() {
fun kotlinObjectWithCompanionObject() {
data class D(val c: C)
val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -104,7 +104,7 @@ class StaticInitialisationOfSerializedObjectTest {
override val classCarpenter = ClassCarpenter(ClassLoader.getSystemClassLoader(), wl2)
}
// This time have the serilization factory and the carpenter use different whitelists
// This time have the serialization factory and the carpenter use different whitelists
@Test
fun deserializeTest2() {
data class D(val c: C2)

View File

@ -35,9 +35,16 @@ fun Schema.mangleNames(names: List<String>): Schema {
return Schema(types = newTypes)
}
/**
* Custom implementation of a [SerializerFactory] where we need to give it a class carpenter
* rather than have it create its own
*/
class SerializerFactoryExternalCarpenter(override val classCarpenter: ClassCarpenter)
: SerializerFactory (classCarpenter.whitelist, classCarpenter.classloader)
open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
var cc = ClassCarpenter(whitelist = whitelist)
var factory = SerializerFactory(AllWhitelist, cc.classloader)
var factory = SerializerFactoryExternalCarpenter(cc)
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
fun testName(): String = Thread.currentThread().stackTrace[2].methodName