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

# Conflicts:
#	client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
#	client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
#	core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java
#	docs/source/changelog.rst
#	docs/source/corda-configuration-file.rst
#	docs/source/upgrade-notes.rst
#	finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
#	finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt
#	gradle/wrapper/gradle-wrapper.properties
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt
#	node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt
#	node/src/integration-test/kotlin/net/corda/node/AuthDBTests.kt
#	node/src/integration-test/kotlin/net/corda/node/BootTests.kt
#	node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt
#	node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt
#	node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
#	node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt
#	node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
#	node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt
#	node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
#	node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt
#	node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt
#	node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt
#	node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
#	node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
#	node/src/main/resources/reference.conf
#	node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
#	node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt
#	node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
#	node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt
#	node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt
#	samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt
#	samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt
#	samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt
#	samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt
#	testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt
#	testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt
#	tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt
#	verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt
#	webserver/src/integration-test/kotlin/net/corda/webserver/WebserverDriverTests.kt
This commit is contained in:
Shams Asari
2018-01-25 17:51:13 +00:00
314 changed files with 4394 additions and 3169 deletions

View File

@ -1,7 +1,6 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
@ -9,7 +8,9 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.slf4j.LoggerFactory
import java.nio.file.Path
import java.security.KeyPair
@ -37,10 +38,9 @@ object DevIdentityGenerator {
}
nodeSslConfig.certificatesDirectory.createDirectories()
nodeSslConfig.createDevKeyStores(legalName)
val (nodeKeyStore) = nodeSslConfig.createDevKeyStores(legalName)
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
val identity = nodeKeyStore.storeLegalIdentity("$NODE_IDENTITY_ALIAS_PREFIX-private-key")
return identity.party
}
@ -78,13 +78,13 @@ object DevIdentityGenerator {
publicKey)
}
val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks"
val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass")
keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert)
keystore.setKeyEntry(
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
keyPair.private,
"cordacadevkeypass".toCharArray(),
arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
keystore.save(distServKeyStoreFile, "cordacadevpass")
X509KeyStore.fromFile(distServKeyStoreFile, "cordacadevpass", createNew = true).update {
setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert)
setPrivateKey(
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
keyPair.private,
listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate),
"cordacadevkeypass")
}
}
}

View File

@ -3,6 +3,7 @@ package net.corda.nodeapi.internal
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.x500Name
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
@ -21,33 +22,51 @@ import javax.security.auth.x500.X500Principal
*/
fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) {
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA): Pair<X509KeyStore, X509KeyStore> {
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
addOrReplaceKey(
val nodeKeyStore = loadNodeKeyStore(createNew = true)
nodeKeyStore.update {
setPrivateKey(
X509Utilities.CORDA_CLIENT_CA,
nodeCaKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(nodeCaCert, intermediateCa.certificate, rootCert))
save(nodeKeystore, keyStorePassword)
listOf(nodeCaCert, intermediateCa.certificate, rootCert))
}
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)
loadOrCreateKeyStore(sslKeystore, keyStorePassword).apply {
addOrReplaceKey(
val sslKeyStore = loadSslKeyStore(createNew = true)
sslKeyStore.update {
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)
setPrivateKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
save(sslKeystore, keyStorePassword)
listOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
}
return Pair(nodeKeyStore, sslKeyStore)
}
fun X509KeyStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.generateKeyPair()): PartyAndCertificate {
val nodeCaCertPath = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
// Assume key password = store password.
val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
// Create new keys and store in keystore.
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
nodeCaCertAndKeyPair.certificate,
nodeCaCertAndKeyPair.keyPair,
nodeCaCertAndKeyPair.certificate.subjectX500Principal,
keyPair.public)
// TODO: X509Utilities.validateCertificateChain()
// Assume key password = store password.
val identityCertPath = listOf(identityCert) + nodeCaCertPath
setPrivateKey(alias, keyPair.private, identityCertPath)
save()
return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath))
}
fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair()
val keyPair = generateKeyPair()
val cert = X509Utilities.createCertificate(
CertificateType.NETWORK_MAP,
rootCa.certificate,

View File

@ -38,7 +38,7 @@ operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T
}
fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
require(clazz.isData) { "Only Kotlin data classes can be parsed" }
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
val constructor = clazz.primaryConstructor!!
val args = constructor.parameters
.filterNot { it.isOptional && !hasPath(it.name!!) }

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.config
import net.corda.core.internal.div
import net.corda.nodeapi.internal.crypto.X509KeyStore
import java.nio.file.Path
interface SSLConfiguration {
@ -11,6 +12,18 @@ interface SSLConfiguration {
// TODO This looks like it should be in NodeSSLConfiguration
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks"
val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks"
fun loadTrustStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(trustStoreFile, trustStorePassword, createNew)
}
fun loadNodeKeyStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(nodeKeystore, keyStorePassword, createNew)
}
fun loadSslKeyStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(sslKeystore, keyStorePassword, createNew)
}
}
interface NodeSSLConfiguration : SSLConfiguration {

View File

@ -3,12 +3,12 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto
import net.corda.core.internal.createDirectories
import net.corda.core.internal.exists
import net.corda.core.internal.read
import net.corda.core.internal.write
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Path
import java.security.*
import java.security.cert.Certificate
@ -30,6 +30,7 @@ fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStor
keyStoreFilePath.read { keyStore.load(it, pass) }
} else {
keyStore.load(null, pass)
keyStoreFilePath.parent.createDirectories()
keyStoreFilePath.write { keyStore.store(it, pass) }
}
return keyStore
@ -101,17 +102,11 @@ fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) {
* @param storePassword password to access the store in future. This does not have to be the same password as any keys stored,
* but for SSL purposes this is recommended.
*/
fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFilePath.write { store(it, storePassword) }
fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray())
/**
* Extract public and private keys from a KeyStore file assuming storage alias is known.
* @param alias The name to lookup the Key and Certificate chain from.
* @param keyPassword Password to unlock the private key entries.
* @return The KeyPair found in the KeyStore under the specified alias.
*/
fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKeyPair(alias, keyPassword).keyPair
fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) {
keyStoreFilePath.write {
store(it, storePassword.toCharArray())
}
}
/**
* Helper method to load a Certificate and KeyPair from their KeyStore.
@ -133,7 +128,7 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi
*/
fun KeyStore.getX509Certificate(alias: String): X509Certificate {
val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\".")
return certificate as? X509Certificate ?: throw IllegalArgumentException("Certificate under alias \"$alias\" is not an X.509 certificate.")
return certificate as? X509Certificate ?: throw IllegalStateException("Certificate under alias \"$alias\" is not an X.509 certificate: $certificate")
}
/**

View File

@ -1,40 +0,0 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.read
import java.nio.file.Path
import java.security.KeyPair
import java.security.cert.Certificate
class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
private val keyStore = storePath.read { loadKeyStore(it, storePassword) }
// TODO This method seems misplaced in this class.
fun storeLegalIdentity(legalName: CordaX500Name, alias: String, keyPair: KeyPair): PartyAndCertificate {
val nodeCaCertChain = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val nodeCa = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
nodeCa.certificate,
nodeCa.keyPair,
legalName.x500Principal,
keyPair.public)
val identityCertPath = X509CertificateFactory().generateCertPath(identityCert, *nodeCaCertChain)
// Assume key password = store password.
keyStore.addOrReplaceKey(alias, keyPair.private, storePassword.toCharArray(), identityCertPath.certificates.toTypedArray())
keyStore.save(storePath, storePassword)
return PartyAndCertificate(identityCertPath)
}
// Delegate methods to keystore. Sadly keystore doesn't have an interface.
fun containsAlias(alias: String) = keyStore.containsAlias(alias)
fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias)
fun getCertificateChain(alias: String): Array<out Certificate> = keyStore.getCertificateChain(alias)
fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
fun getCertificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
}

View File

@ -0,0 +1,78 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto
import net.corda.core.internal.uncheckedCast
import java.nio.file.Path
import java.security.KeyPair
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
/**
* Wrapper around a [KeyStore] object but only dealing with [X509Certificate]s and with a better API.
*/
class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null) {
/** Wrap an existing [KeyStore]. [save] is not supported. */
constructor(keyStore: KeyStore, storePassword: String) : this(keyStore, storePassword, null)
/** Create an empty [KeyStore] using the given password. [save] is not supported. */
constructor(storePassword: String) : this(
KeyStore.getInstance(KEYSTORE_TYPE).apply { load(null, storePassword.toCharArray()) },
storePassword
)
companion object {
/**
* Read a [KeyStore] from the given file. If the key store doesn't exist and [createNew] is true then a blank
* key store will be written out. Changes to the returned [X509KeyStore] can be persisted with [save].
*/
fun fromFile(keyStoreFile: Path, storePassword: String, createNew: Boolean = false): X509KeyStore {
val internal: KeyStore = if (createNew) loadOrCreateKeyStore(keyStoreFile, storePassword) else loadKeyStore(keyStoreFile, storePassword)
return X509KeyStore(internal, storePassword, keyStoreFile)
}
}
operator fun contains(alias: String): Boolean = internal.containsAlias(alias)
fun aliases(): Iterator<String> = internal.aliases().iterator()
fun getCertificate(alias: String): X509Certificate = internal.getX509Certificate(alias)
fun getCertificateChain(alias: String): List<X509Certificate> {
val certArray = requireNotNull(internal.getCertificateChain(alias)) { "No certificate chain under the alias $alias" }
check(certArray.all { it is X509Certificate }) { "Certificate chain under alias $alias is not X.509" }
return uncheckedCast(certArray.asList())
}
fun getCertificateAndKeyPair(alias: String, keyPassword: String = storePassword): CertificateAndKeyPair {
val cert = getCertificate(alias)
val publicKey = Crypto.toSupportedPublicKey(cert.publicKey)
return CertificateAndKeyPair(cert, KeyPair(publicKey, getPrivateKey(alias, keyPassword)))
}
fun getPrivateKey(alias: String, keyPassword: String = storePassword): PrivateKey {
return internal.getSupportedKey(alias, keyPassword)
}
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String = storePassword) {
internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray())
}
fun setCertificate(alias: String, certificate: X509Certificate) {
internal.setCertificateEntry(alias, certificate)
}
fun save() {
internal.save(checkWritableToFile(), storePassword)
}
fun update(action: X509KeyStore.() -> Unit) {
checkWritableToFile()
action(this)
save()
}
private fun checkWritableToFile(): Path {
return keyStoreFile ?: throw IllegalStateException("This key store cannot be written to")
}
}

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.CertRole
import net.corda.core.internal.reader
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.writer
import net.corda.core.utilities.days
import net.corda.core.utilities.millis
@ -93,14 +94,19 @@ object X509Utilities {
return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
}
@Throws(CertPathValidatorException::class)
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: X509Certificate) {
validateCertificateChain(trustedRoot, certificates.asList())
}
fun validateCertificateChain(trustedRoot: X509Certificate, certificates: List<X509Certificate>) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
validateCertPath(trustedRoot, buildCertPath(certificates))
}
fun validateCertPath(trustedRoot: X509Certificate, certPath: CertPath) {
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
params.isRevocationEnabled = false
val certPath = X509CertificateFactory().generateCertPath(*certificates)
val pathValidator = CertPathValidator.getInstance("PKIX")
pathValidator.validate(certPath, params)
CertPathValidator.getInstance("PKIX").validate(certPath, params)
}
/**
@ -264,6 +270,21 @@ object X509Utilities {
fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
}
fun buildCertPath(first: X509Certificate, remaining: List<X509Certificate>): CertPath {
val certificates = ArrayList<X509Certificate>(1 + remaining.size)
certificates += first
certificates += remaining
return buildCertPath(certificates)
}
fun buildCertPath(vararg certificates: X509Certificate): CertPath {
return X509CertificateFactory().generateCertPath(*certificates)
}
fun buildCertPath(certificates: List<X509Certificate>): CertPath {
return X509CertificateFactory().generateCertPath(certificates)
}
}
/**
@ -274,6 +295,16 @@ object X509Utilities {
fun X509Certificate.toBc() = X509CertificateHolder(encoded)
fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream())
val CertPath.x509Certificates: List<X509Certificate> get() {
require(type == "X.509") { "Not an X.509 cert path: $this" }
// We're not mapping the list to avoid creating a new one.
return uncheckedCast(certificates)
}
val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certificate) { "Not an X.509 certificate: $this" }
val Array<Certificate>.x509: List<X509Certificate> get() = map { it.x509 }
/**
* Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best
* so assume this class is not.
@ -281,17 +312,11 @@ fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().ge
class X509CertificateFactory {
val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
fun generateCertificate(input: InputStream): X509Certificate {
return delegate.generateCertificate(input) as X509Certificate
}
fun generateCertificate(input: InputStream): X509Certificate = delegate.generateCertificate(input).x509
fun generateCertPath(certificates: List<Certificate>): CertPath {
return delegate.generateCertPath(certificates)
}
fun generateCertPath(vararg certificates: X509Certificate): CertPath = generateCertPath(certificates.asList())
fun generateCertPath(vararg certificates: Certificate): CertPath {
return delegate.generateCertPath(certificates.asList())
}
fun generateCertPath(certificates: List<X509Certificate>): CertPath = delegate.generateCertPath(certificates)
}
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean, val role: CertRole?) {

View File

@ -70,8 +70,8 @@ class CorDappCustomSerializer(
data.withDescribed(descriptor) {
data.withList {
for (property in proxySerializer.propertySerializers.getters) {
property.writeProperty(proxy, this, output)
proxySerializer.propertySerializers.serializationOrder.forEach {
it.getter.writeProperty(proxy, this, output)
}
}
}

View File

@ -61,7 +61,14 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == this.clazz
override val type: Type get() = clazz
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForDescriptors(superClassSerializer.typeDescriptor.toString(), nameForType(clazz))}")
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(clazz), null, emptyList(), SerializerFactory.nameForType(superClassSerializer.type), Descriptor(typeDescriptor), emptyList())
private val typeNotation: TypeNotation = RestrictedType(
SerializerFactory.nameForType(clazz),
null,
emptyList(),
SerializerFactory.nameForType(superClassSerializer.type),
Descriptor(typeDescriptor),
emptyList())
override fun writeClassInfo(output: SerializationOutput) {
output.writeTypeNotations(typeNotation)
}
@ -132,8 +139,8 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput) {
val proxy = toProxy(obj)
data.withList {
for (property in proxySerializer.propertySerializers.getters) {
property.writeProperty(proxy, this, output)
proxySerializer.propertySerializers.serializationOrder.forEach {
it.getter.writeProperty(proxy, this, output)
}
}
}

View File

@ -20,7 +20,7 @@ class EvolutionSerializer(
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 = ConstructorDestructorMethods (emptyList(), emptyList())
override val propertySerializers = PropertySerializersEvolution()
/**
* Represents a parameter as would be passed to the constructor of the class as it was

View File

@ -11,7 +11,8 @@ 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).
* Responsible for serializing and deserializing a regular object instance via a series of properties
* (matched with a constructor).
*/
open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
override val type: Type get() = clazz
@ -22,7 +23,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val logger = contextLogger()
}
open internal val propertySerializers: ConstructorDestructorMethods by lazy {
open internal val propertySerializers: PropertySerializers by lazy {
propertiesForSerialization(kotlinConstructor, clazz, factory)
}
@ -31,17 +32,22 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val typeName = nameForType(clazz)
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
private val interfaces = interfacesForSerialization(clazz, factory) // We restrict to only those annotated or whitelisted
open internal val typeNotation: TypeNotation by lazy { CompositeType(typeName, null, generateProvides(), Descriptor(typeDescriptor), generateFields()) }
// We restrict to only those annotated or whitelisted
private val interfaces = interfacesForSerialization(clazz, factory)
open internal val typeNotation: TypeNotation by lazy {
CompositeType(typeName, null, generateProvides(), Descriptor(typeDescriptor), generateFields())
}
override fun writeClassInfo(output: SerializationOutput) {
if (output.writeTypeNotations(typeNotation)) {
for (iface in interfaces) {
output.requireSerializer(iface)
}
for (property in propertySerializers.getters) {
property.writeClassInfo(output)
propertySerializers.serializationOrder.forEach { property ->
property.getter.writeClassInfo(output)
}
}
}
@ -51,8 +57,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
data.withDescribed(typeNotation.descriptor) {
// Write list
withList {
for (property in propertySerializers.getters) {
property.writeProperty(obj, this, output)
propertySerializers.serializationOrder.forEach { property ->
property.getter.writeProperty(obj, this, output)
}
}
}
@ -63,16 +69,18 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
schemas: SerializationSchemas,
input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) {
if (obj is List<*>) {
if (obj.size > propertySerializers.getters.size) {
if (obj.size > propertySerializers.size) {
throw NotSerializableException("Too many properties in described type $typeName")
}
return if (propertySerializers.setters.isEmpty()) {
return if (propertySerializers.byConstructor) {
readObjectBuildViaConstructor(obj, schemas, input)
} else {
readObjectBuildViaSetters(obj, schemas, input)
}
} else throw NotSerializableException("Body of described type is unexpected $obj")
} else {
throw NotSerializableException("Body of described type is unexpected $obj")
}
}
private fun readObjectBuildViaConstructor(
@ -81,7 +89,11 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
input: DeserializationInput) : Any = ifThrowsAppend({ clazz.typeName }){
logger.trace { "Calling construction based construction for ${clazz.typeName}" }
return construct(obj.zip(propertySerializers.getters).map { it.second.readProperty(it.first, schemas, input) })
return construct (propertySerializers.serializationOrder
.zip(obj)
.map { Pair(it.first.initialPosition, it.first.getter.readProperty(it.second, schemas, input)) }
.sortedWith(compareBy({it.first}))
.map { it.second })
}
private fun readObjectBuildViaSetters(
@ -93,22 +105,23 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
val instance : Any = javaConstructor?.newInstance() ?: throw NotSerializableException (
"Failed to instantiate instance of object $clazz")
// read the properties out of the serialised form
// read the properties out of the serialised form, since we're invoking the setters the order we
// do it in doesn't matter
val propertiesFromBlob = obj
.zip(propertySerializers.getters)
.map { it.second.readProperty(it.first, schemas, input) }
.zip(propertySerializers.serializationOrder)
.map { it.second.getter.readProperty(it.first, schemas, input) }
// one by one take a property and invoke the setter on the class
propertySerializers.setters.zip(propertiesFromBlob).forEach {
it.first?.invoke(instance, *listOf(it.second).toTypedArray())
propertySerializers.serializationOrder.zip(propertiesFromBlob).forEach {
it.first.set(instance, it.second)
}
return instance
}
private fun generateFields(): List<Field> {
return propertySerializers.getters.map {
Field(it.name, it.type, it.requires, it.default, null, it.mandatory, false)
return propertySerializers.serializationOrder.map {
Field(it.getter.name, it.getter.type, it.getter.requires, it.getter.default, null, it.getter.mandatory, false)
}
}

View File

@ -1,69 +1,8 @@
package net.corda.nodeapi.internal.serialization.amqp
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.
@ -106,13 +45,13 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
companion object {
fun make(name: String, readMethod: PropertyReader, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
if (SerializerFactory.isPrimitive(resolvedType)) {
return when (resolvedType) {
return if (SerializerFactory.isPrimitive(resolvedType)) {
when (resolvedType) {
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
else -> AMQPPrimitivePropertySerializer(name, readMethod, resolvedType)
}
} else {
return DescribedTypePropertySerializer(name, readMethod, resolvedType) { factory.get(null, resolvedType) }
DescribedTypePropertySerializer(name, readMethod, resolvedType) { factory.get(null, resolvedType) }
}
}
}

View File

@ -0,0 +1,179 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.loggerFor
import java.io.NotSerializableException
import java.lang.reflect.Method
import java.lang.reflect.Type
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.kotlinProperty
import java.lang.reflect.Field
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
}
}
/**
* Represents a generic interface to a serializable property of an object.
*
* @property initialPosition where in the constructor used for serialization the property occurs.
* @property getter a [PropertySerializer] wrapping access to the property. This will either be a
* method invocation on the getter or, if not publicly accessible, reflection based by temporally
* making the property accessible.
*/
abstract class PropertyAccessor(
val initialPosition: Int,
open val getter: PropertySerializer) {
companion object : Comparator<PropertyAccessor> {
override fun compare(p0: PropertyAccessor?, p1: PropertyAccessor?): Int {
return p0?.getter?.name?.compareTo(p1?.getter?.name ?: "") ?: 0
}
}
/**
* Override to control how the property is set on the object.
*/
abstract fun set(instance: Any, obj: Any?)
override fun toString(): String {
return "${getter.name}($initialPosition)"
}
}
/**
* Implementation of [PropertyAccessor] representing a property of an object that
* is serialized and deserialized via JavaBean getter and setter style methods.
*/
class PropertyAccessorGetterSetter(
initialPosition: Int,
getter: PropertySerializer,
private val setter: Method?) : PropertyAccessor(initialPosition, getter) {
/**
* Invokes the setter on the underlying object passing in the serialized value.
*/
override fun set(instance: Any, obj: Any?) {
setter?.invoke(instance, *listOf(obj).toTypedArray())
}
}
/**
* Implementation of [PropertyAccessor] representing a property of an object that
* is serialized via a JavaBean getter but deserialized using the constructor
* of the object the property belongs to.
*/
class PropertyAccessorConstructor(
initialPosition: Int,
override val getter: PropertySerializer) : PropertyAccessor(initialPosition, getter) {
/**
* Because the property should be being set on the obejct through the constructor any
* calls to the explicit setter should be an error.
*/
override fun set(instance: Any, obj: Any?) {
NotSerializableException ("Attempting to access a setter on an object being instantiated " +
"via its constructor.")
}
}
/**
* Represents a collection of [PropertyAccessor]s that represent the serialized form
* of an object.
*
* @property serializationOrder a list of [PropertyAccessor]. For deterministic serialization
* should be sorted.
* @property size how many properties are being serialized.
* @property byConstructor are the properties of the class represented by this set of properties populated
* on deserialization via the object's constructor or the corresponding setter functions. Should be
* overridden and set appropriately by child types.
*/
abstract class PropertySerializers(
val serializationOrder: List<PropertyAccessor>) {
companion object {
fun make(serializationOrder: List<PropertyAccessor>) =
when (serializationOrder.firstOrNull()) {
is PropertyAccessorConstructor -> PropertySerializersConstructor(serializationOrder)
is PropertyAccessorGetterSetter -> PropertySerializersSetter(serializationOrder)
null -> PropertySerializersNoProperties()
else -> {
throw NotSerializableException ("Unknown Property Accessor type, cannot create set")
}
}
}
val size get() = serializationOrder.size
abstract val byConstructor: Boolean
}
class PropertySerializersNoProperties : PropertySerializers (emptyList()) {
override val byConstructor get() = true
}
class PropertySerializersConstructor(
serializationOrder: List<PropertyAccessor>) : PropertySerializers(serializationOrder) {
override val byConstructor get() = true
}
class PropertySerializersSetter(
serializationOrder: List<PropertyAccessor>) : PropertySerializers(serializationOrder) {
override val byConstructor get() = false
}
class PropertySerializersEvolution : PropertySerializers(emptyList()) {
override val byConstructor get() = false
}

View File

@ -446,11 +446,12 @@ private fun fingerprintForObject(
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
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
.serializationOrder
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, offset+4)
.putUnencodedChars(prop.name)
.putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, offset+4)
.putUnencodedChars(prop.getter.name)
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) }
return hasher

View File

@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken
import io.netty.util.internal.EmptyArrays
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
@ -20,7 +19,6 @@ 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,10 +27,6 @@ import kotlin.reflect.jvm.kotlinProperty
@Retention(AnnotationRetention.RUNTIME)
annotation class ConstructorForDeserialization
data class ConstructorDestructorMethods(
val getters: Collection<PropertySerializer>,
val setters: Collection<Method?>)
/**
* Code for finding the constructor we will use for deserialization.
*
@ -74,17 +68,22 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
* Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters have
* names accessible via reflection.
*/
internal fun <T : Any> propertiesForSerialization(kotlinConstructor: KFunction<T>?, type: Type, factory: SerializerFactory): ConstructorDestructorMethods {
val clazz = type.asClass()!!
return if (kotlinConstructor != null) propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) else propertiesForSerializationFromAbstract(clazz, type, factory)
}
internal fun <T : Any> propertiesForSerialization(
kotlinConstructor: KFunction<T>?,
type: Type,
factory: SerializerFactory) = PropertySerializers.make (
if (kotlinConstructor != null) {
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
} else {
propertiesForSerializationFromAbstract(type.asClass()!!, type, factory)
}.sortedWith(PropertyAccessor))
fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
private fun <T : Any> propertiesForSerializationFromConstructor(
internal fun <T : Any> propertiesForSerializationFromConstructor(
kotlinConstructor: KFunction<T>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
factory: SerializerFactory): List<PropertyAccessor> {
val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType
// 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
@ -96,39 +95,45 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
return propertiesForSerializationFromSetters(properties, type, factory)
}
val rc: MutableList<PropertySerializer> = ArrayList(kotlinConstructor.parameters.size)
return mutableListOf<PropertyAccessor>().apply {
kotlinConstructor.parameters.withIndex().forEach { param ->
val name = param.value.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
for (param in kotlinConstructor.parameters) {
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
val propertyReader = if (name in properties) {
// it's a publicly accessible property
val matchingProperty = properties[name]!!
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.")
// 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)
val returnType = resolveTypeVariables(getter.genericReturnType, type)
if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) {
throw NotSerializableException(
"Property type $returnType for $name of $clazz differs from constructor parameter "
+ "type ${param.value.type.javaType}")
}
Pair(PublicPropertyReader(getter), returnType)
} else {
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
try {
val field = clazz.getDeclaredField(param.value.name)
Pair(PrivatePropertyReader(field, type), field.genericType)
} 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")
}
}
} else {
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")
}
this += PropertyAccessorConstructor(
param.index,
PropertySerializer.make(name, propertyReader.first, propertyReader.second, factory))
}
}
return ConstructorDestructorMethods(rc, emptyList())
}
/**
@ -138,26 +143,26 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
private fun propertiesForSerializationFromSetters(
properties: Map<String, PropertyDescriptor>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
val getters: MutableList<PropertySerializer> = ArrayList(properties.size)
val setters: MutableList<Method?> = ArrayList(properties.size)
factory: SerializerFactory): List<PropertyAccessor> {
return mutableListOf<PropertyAccessorGetterSetter>().apply {
var idx = 0
properties.forEach { property ->
val getter: Method? = property.value.readMethod
val setter: Method? = property.value.writeMethod
properties.forEach { property ->
val getter: Method? = property.value.readMethod
val setter: Method? = property.value.writeMethod
if (getter == null || setter == null) return@forEach
if (getter == null || setter == null) return@forEach
// NOTE: There is no need to check return and parameter types vs the underlying type for
// 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
// NOTE: There is no need to check return and parameter types vs the underlying type for
// 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, PublicPropertyReader(getter),
resolveTypeVariables(getter.genericReturnType, type), factory)
setters += setter
this += PropertyAccessorGetterSetter (
idx++,
PropertySerializer.make(property.key, PublicPropertyReader(getter),
resolveTypeVariables(getter.genericReturnType, type), factory),
setter)
}
}
return ConstructorDestructorMethods(getters, setters)
}
private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean {
@ -168,21 +173,24 @@ private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawG
private fun propertiesForSerializationFromAbstract(
clazz: Class<*>,
type: Type,
factory: SerializerFactory): ConstructorDestructorMethods {
factory: SerializerFactory): List<PropertyAccessor> {
// 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 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 returnType = resolveTypeVariables(getter.genericReturnType, type)
rc += PropertySerializer.make(property.name, PublicPropertyReader(getter), returnType, factory)
}
return ConstructorDestructorMethods(rc, emptyList())
return mutableListOf<PropertyAccessorConstructor>().apply {
properties.withIndex().forEach { property ->
// Check that the method has a getter in java.
val getter = property.value.readMethod ?: throw NotSerializableException(
"Property has no getter method for ${property.value.name} of $clazz.")
val returnType = resolveTypeVariables(getter.genericReturnType, type)
this += PropertyAccessorConstructor(
property.index,
PropertySerializer.make(property.value.name, PublicPropertyReader(getter), returnType, factory))
}
}
}
internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> {

View File

@ -23,9 +23,8 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
// Try and find a constructor
try {
val constructor = constructorForDeserialization(obj.javaClass)
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
for (prop in props.getters) {
extraProperties[prop.name] = prop.propertyReader.read(obj)
propertiesForSerializationFromConstructor(constructor!!, obj.javaClass, factory).forEach { property ->
extraProperties[property.getter.name] = property.getter.propertyReader.read(obj)
}
} catch (e: NotSerializableException) {
logger.warn("Unexpected exception", e)
@ -90,4 +89,4 @@ class StackTraceElementSerializer(factory: SerializerFactory) : CustomSerializer
override fun fromProxy(proxy: StackTraceElementProxy): StackTraceElement = StackTraceElement(proxy.declaringClass, proxy.methodName, proxy.fileName, proxy.lineNumber)
data class StackTraceElementProxy(val declaringClass: String, val methodName: String, val fileName: String?, val lineNumber: Int)
}
}

View File

@ -4,7 +4,7 @@ import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.testing.SerializationEnvironmentRule;
import net.corda.testing.core.SerializationEnvironmentRule;
import net.corda.nodeapi.internal.serialization.kryo.CordaClosureBlacklistSerializer;
import net.corda.nodeapi.internal.serialization.kryo.KryoSerializationSchemeKt;
import org.junit.Before;
@ -25,7 +25,7 @@ public final class ForbiddenLambdaSerializationTests {
@Before
public void setup() {
factory = testSerialization.getEnv().getSerializationFactory();
factory = testSerialization.getSerializationFactory();
}
@Test

View File

@ -4,7 +4,7 @@ import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.testing.SerializationEnvironmentRule;
import net.corda.testing.core.SerializationEnvironmentRule;
import net.corda.nodeapi.internal.serialization.kryo.CordaClosureSerializer;
import net.corda.nodeapi.internal.serialization.kryo.KryoSerializationSchemeKt;
import org.junit.Before;
@ -25,7 +25,7 @@ public final class LambdaCheckpointSerializationTest {
@Before
public void setup() {
factory = testSerialization.getEnv().getSerializationFactory();
factory = testSerialization.getSerializationFactory();
context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint);
}

View File

@ -47,9 +47,9 @@ public class JavaPrivatePropertyTests {
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);
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
assertTrue (((PropertyAccessor)propertyReaders[0]).getGetter().getPropertyReader() instanceof PrivatePropertyReader);
}
@Test
@ -76,8 +76,8 @@ public class JavaPrivatePropertyTests {
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);
assertEquals(1, cSerializer.getPropertySerializers().getSerializationOrder().size());
Object[] propertyReaders = cSerializer.getPropertySerializers().getSerializationOrder().toArray();
assertTrue (((PropertyAccessor)propertyReaders[0]).getGetter().getPropertyReader() instanceof PublicPropertyReader);
}
}

View File

@ -13,7 +13,9 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.*
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import net.corda.testing.services.MockAttachmentStorage
import org.junit.Assert.*

View File

@ -18,7 +18,9 @@ import net.corda.nodeapi.DummyContractBackdoor
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.*
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.kryoSpecific
import net.corda.testing.internal.rigorousMock
import net.corda.testing.services.MockAttachmentStorage

View File

@ -1,9 +1,9 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.Crypto
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.TestNodeInfoBuilder
import net.corda.testing.internal.signWith
import org.assertj.core.api.Assertions.assertThat

View File

@ -15,9 +15,9 @@ import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.TestIdentity
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x509.BasicConstraints
@ -222,7 +222,8 @@ class X509UtilitiesTest {
val clientSocketFactory = context.socketFactory
val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket
val serverParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2"))
val serverParams = SSLParameters(CIPHER_SUITES,
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true
serverParams.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
@ -230,7 +231,8 @@ class X509UtilitiesTest {
serverSocket.useClientMode = false
val clientSocket = clientSocketFactory.createSocket() as SSLSocket
val clientParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2"))
val clientParams = SSLParameters(CIPHER_SUITES,
arrayOf("TLSv1.2"))
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true
@ -264,10 +266,10 @@ class X509UtilitiesTest {
assertTrue(clientSocket.isConnected)
// Double check hostname manually
val peerChain = clientSocket.session.peerCertificates
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
val peerChain = clientSocket.session.peerCertificates.x509
val peerX500Principal = peerChain[0].subjectX500Principal
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain)
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World")
var timeout = 0
@ -336,7 +338,7 @@ class X509UtilitiesTest {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey)
val expected = X509CertificateFactory().generateCertPath(certificate, rootCACert)
val expected = X509Utilities.buildCertPath(certificate, rootCACert)
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)

View File

@ -3,8 +3,8 @@ package net.corda.nodeapi.internal.serialization
import net.corda.core.contracts.ContractAttachment
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.*
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat
@ -25,10 +25,11 @@ class ContractAttachmentSerializerTest {
private lateinit var context: SerializationContext
private lateinit var contextWithToken: SerializationContext
private val mockServices = MockServices(emptyList(), rigorousMock(), CordaX500Name("MegaCorp", "London", "GB"))
@Before
fun setup() {
factory = testSerialization.env.serializationFactory
context = testSerialization.env.checkpointContext
factory = testSerialization.serializationFactory
context = testSerialization.checkpointContext
contextWithToken = context.withTokenContext(SerializeAsTokenContextImpl(Any(), factory, context, mockServices))
}

View File

@ -14,9 +14,9 @@ import net.corda.core.utilities.sequence
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.ALICE_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.TestIdentity
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before

View File

@ -10,7 +10,7 @@ import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.internal.amqpSpecific
import net.corda.testing.internal.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals

View File

@ -8,7 +8,7 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.amqpSpecific
import net.corda.testing.internal.kryoSpecific
import org.assertj.core.api.Assertions.assertThatThrownBy

View File

@ -4,7 +4,7 @@ import net.corda.core.crypto.Crypto
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test

View File

@ -9,7 +9,7 @@ import net.corda.nodeapi.internal.serialization.kryo.CordaKryo
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.internal.rigorousMock
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
@ -25,8 +25,8 @@ class SerializationTokenTest {
@Before
fun setup() {
factory = testSerialization.env.serializationFactory
context = testSerialization.env.checkpointContext.withWhitelisted(SingletonSerializationToken::class.java)
factory = testSerialization.serializationFactory
context = testSerialization.checkpointContext.withWhitelisted(SingletonSerializationToken::class.java)
}
// Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized

View File

@ -8,6 +8,8 @@ import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.internal.kryoSpecific
import net.corda.testing.internal.kryoSpecific
import net.corda.testing.core.SerializationEnvironmentRule
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Rule

View File

@ -2,12 +2,23 @@ package net.corda.nodeapi.internal.serialization.amqp
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.assertEquals
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.junit.Test
import org.apache.qpid.proton.amqp.Symbol
import java.util.concurrent.ConcurrentHashMap
class PrivatePropertyTests {
private val factory = testDefaultFactory()
private val factory = testDefaultFactoryNoEvolution()
companion object {
val fields : Map<String, java.lang.reflect.Field> = mapOf (
"serializersByDesc" to SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")).apply {
this.values.forEach {
it.isAccessible = true
}
}
}
@Test
fun testWithOnePrivateProperty() {
@ -53,16 +64,14 @@ class PrivatePropertyTests {
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 serializersByDescriptor = fields["serializersByDesc"]?.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()
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.getter }
assertEquals(2, propertySerializers.size)
// a was public so should have a synthesised getter
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
@ -84,16 +93,14 @@ class PrivatePropertyTests {
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 serializersByDescriptor = fields["serializersByDesc"]?.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()
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.getter }
assertEquals(2, propertySerializers.size)
// as before, a is public so we'll use the getter method
@ -105,13 +112,24 @@ class PrivatePropertyTests {
}
}
@Suppress("UNCHECKED_CAST")
@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))
val output = SerializationOutput(factory).serializeAndReturnSchema(c1)
println (output.schema)
val serializersByDescriptor = fields["serializersByDesc"]!!.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
// Inner and Outer
assertEquals(2, serializersByDescriptor.size)
val schemaDescriptor = output.schema.types.first().descriptor.name
val c2 = DeserializationInput(factory).deserialize(output.obj)
assertEquals(c1, c2)
}
}

View File

@ -20,8 +20,10 @@ import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import net.corda.nodeapi.internal.serialization.GeneratedAttachment
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl

View File

@ -0,0 +1,125 @@
package net.corda.nodeapi.internal.serialization.amqp
import org.junit.Test
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
import org.apache.qpid.proton.amqp.Symbol
import java.lang.reflect.Method
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class SerializationPropertyOrdering {
companion object {
val VERBOSE get() = false
val sf = testDefaultFactoryNoEvolution()
}
// Force object references to be ued to ensure we go through that code path
// this test shows (not now it's fixed) a bug whereby deserializing objects
// would break where refferenced objects were accessed before they'd been
// processed thanks to the way the blob was deserialized
@Test
fun refferenceOrdering() {
data class Reffed(val c: String, val b: String, val a: String)
data class User(val b: List<Reffed>, val a: List<Reffed>)
val r1 = Reffed("do not", "or", "do")
val r2 = Reffed("do not", "or", "do")
val l = listOf(r1, r2, r1, r2, r1, r2)
val u = User(l,l)
val output = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(u)
val input = DeserializationInput(sf).deserialize(output.obj)
}
@Test
fun randomOrder() {
data class C(val c: Int, val d: Int, val b: Int, val e: Int, val a: Int)
val c = C(3,4,2,5,1)
val output = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
// the schema should reflect the serialized order of properties, not the
// construction order
assertEquals(1, output.schema.types.size)
output.schema.types.firstOrNull()?.apply {
assertEquals(5, (this as CompositeType).fields.size)
assertEquals("a", this.fields[0].name)
assertEquals("b", this.fields[1].name)
assertEquals("c", this.fields[2].name)
assertEquals("d", this.fields[3].name)
assertEquals("e", this.fields[4].name)
}
// and deserializing it should construct the object as it was and not in the order prescribed
// by the serialized form
val input = DeserializationInput(sf).deserialize(output.obj)
assertEquals(1, input.a)
assertEquals(2, input.b)
assertEquals(3, input.c)
assertEquals(4, input.d)
assertEquals(5, input.e)
}
@Suppress("UNCHECKED_CAST")
@Test
fun randomOrderSetter() {
data class C(var c: Int, var d: Int, var b: Int, var e: Int, var a: Int) {
// This will force the serialization engine to use getter / setter
// instantiation for the object rather than construction
@ConstructorForDeserialization
@Suppress("UNUSED")
constructor() : this(0, 0, 0, 0, 0)
}
val c = C()
c.a = 100
c.b = 200
c.c = 300
c.d = 400
c.e = 500
val output = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c)
// the schema should reflect the serialized order of properties, not the
// construction order
assertEquals(1, output.schema.types.size)
output.schema.types.firstOrNull()?.apply {
assertEquals(5, (this as CompositeType).fields.size)
assertEquals("a", this.fields[0].name)
assertEquals("b", this.fields[1].name)
assertEquals("c", this.fields[2].name)
assertEquals("d", this.fields[3].name)
assertEquals("e", this.fields[4].name)
}
// Test needs to look at a bunch of private variables, change the access semantics for them
val fields : Map<String, java.lang.reflect.Field> = mapOf (
"serializersByDesc" to SerializerFactory::class.java.getDeclaredField("serializersByDescriptor"),
"setter" to PropertyAccessorGetterSetter::class.java.getDeclaredField("setter")).apply {
this.values.forEach {
it.isAccessible = true
}
}
val serializersByDescriptor = fields["serializersByDesc"]!!.get(sf) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
val schemaDescriptor = output.schema.types.first().descriptor.name
// make sure that each property accessor has a setter to ensure we're using getter / setter instantiation
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, this.size)
assertTrue(this.first() is ObjectSerializer)
val propertyAccessors = (this.first() as ObjectSerializer).propertySerializers.serializationOrder as List<PropertyAccessorGetterSetter>
propertyAccessors.forEach { property -> assertNotNull(fields["setter"]!!.get(property) as Method?) }
}
val input = DeserializationInput(sf).deserialize(output.obj)
assertEquals(100, input.a)
assertEquals(200, input.b)
assertEquals(300, input.c)
assertEquals(400, input.d)
assertEquals(500, input.e)
}
}