diff --git a/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallInstance.kt b/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallInstance.kt index 2949e3b012..2cd50acf73 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallInstance.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallInstance.kt @@ -13,7 +13,7 @@ import net.corda.core.internal.div import net.corda.core.internal.exists import net.corda.core.internal.readObject import net.corda.core.serialization.deserialize -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.contextLogger @@ -57,7 +57,7 @@ class FirewallInstance(val conf: FirewallConfiguration, } if (!serializationExists) { val classloader = this.javaClass.classLoader - nodeSerializationEnv = SerializationEnvironmentImpl( + nodeSerializationEnv = SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPFirewallSerializationScheme(emptyList())) }, diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt index 2212638551..e56711a50d 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt @@ -98,7 +98,7 @@ private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() val ctor = constructorForDeserialization(beanClass) val amqpProperties = propertiesForSerialization(ctor, beanClass, serializerFactory) .serializationOrder - .map { it.serializer.name } + .mapNotNull { if (it.isCalculated) null else it.serializer.name } val propertyRenames = beanDesc.findProperties().associateBy({ it.name }, { it.internalName }) (amqpProperties - propertyRenames.values).let { check(it.isEmpty()) { "Jackson didn't provide serialisers for $it" } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt index df12645479..a0e2bfc307 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt @@ -6,7 +6,6 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext.* import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.internal.SerializationEnvironment -import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.serialization.internal.* import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme @@ -35,7 +34,7 @@ class AMQPClientSerializationScheme( } fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment { - return SerializationEnvironmentImpl( + return SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPClientSerializationScheme(emptyList())) }, diff --git a/confidential-identities/build.gradle b/confidential-identities/build.gradle index 6b5616d000..38a2f43807 100644 --- a/confidential-identities/build.gradle +++ b/confidential-identities/build.gradle @@ -20,7 +20,6 @@ dependencies { testCompile "com.google.guava:guava-testlib:$guava_version" // Bring in the MockNode infrastructure for writing protocol unit tests. - testCompile project(":node") testCompile project(":node-driver") // AssertJ: for fluent assertions for testing diff --git a/core-deterministic/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationFactory.kt b/core-deterministic/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationFactory.kt deleted file mode 100644 index dbb6fb54c0..0000000000 --- a/core-deterministic/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationFactory.kt +++ /dev/null @@ -1,74 +0,0 @@ -package net.corda.core.serialization.internal - -import net.corda.core.KeepForDJVM -import net.corda.core.serialization.SerializedBytes -import net.corda.core.utilities.ByteSequence -import java.io.NotSerializableException - -/** - * A deterministic version of [CheckpointSerializationFactory] that does not use thread-locals to manage serialization - * context. - */ -@KeepForDJVM -class CheckpointSerializationFactory( - private val scheme: CheckpointSerializationScheme -) { - - val defaultContext: CheckpointSerializationContext get() = _currentContext ?: effectiveSerializationEnv.checkpointContext - - private val creator: List = Exception().stackTrace.asList() - - /** - * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. - * - * @param byteSequence The bytes to deserialize, including a format header prefix. - * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. - * @param context A context that configures various parameters to deserialization. - */ - @Throws(NotSerializableException::class) - fun deserialize(byteSequence: ByteSequence, clazz: Class, context: CheckpointSerializationContext): T { - return withCurrentContext(context) { scheme.deserialize(byteSequence, clazz, context) } - } - - /** - * Serialize an object to bytes using the preferred serialization format version from the context. - * - * @param obj The object to be serialized. - * @param context A context that configures various parameters to serialization, including the serialization format version. - */ - fun serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes { - return withCurrentContext(context) { scheme.serialize(obj, context) } - } - - override fun toString(): String { - return "${this.javaClass.name} scheme=$scheme ${creator.joinToString("\n")}" - } - - override fun equals(other: Any?): Boolean { - return other is CheckpointSerializationFactory && other.scheme == this.scheme - } - - override fun hashCode(): Int = scheme.hashCode() - - private var _currentContext: CheckpointSerializationContext? = null - - /** - * Change the current context inside the block to that supplied. - */ - fun withCurrentContext(context: CheckpointSerializationContext?, block: () -> T): T { - val priorContext = _currentContext - if (context != null) _currentContext = context - try { - return block() - } finally { - if (context != null) _currentContext = priorContext - } - } - - companion object { - /** - * A default factory for serialization/deserialization. - */ - val defaultFactory: CheckpointSerializationFactory get() = effectiveSerializationEnv.checkpointSerializationFactory - } -} \ No newline at end of file diff --git a/core-deterministic/testing/data/build.gradle b/core-deterministic/testing/data/build.gradle index 59992d92eb..03da2a8977 100644 --- a/core-deterministic/testing/data/build.gradle +++ b/core-deterministic/testing/data/build.gradle @@ -22,6 +22,9 @@ test { // Running this class is the whole point, so include it explicitly. includeTestsMatching "net.corda.deterministic.data.GenerateData" } + // force execution of these tests to generate artifacts required by other module (eg. VerifyTransactionTest) + // note: required by Gradle Build Cache. + outputs.upToDateWhen { false } } assemble.finalizedBy test diff --git a/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt index 15848a4be4..e388c7c6d9 100644 --- a/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt +++ b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt @@ -4,7 +4,7 @@ import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext.UseCase.P2P import net.corda.core.serialization.SerializationCustomSerializer -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.serialization.internal.* import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme @@ -58,13 +58,11 @@ class LocalSerializationRule(private val label: String) : TestRule { _contextSerializationEnv.set(null) } - private fun createTestSerializationEnv(): SerializationEnvironmentImpl { + private fun createTestSerializationEnv(): SerializationEnvironment { val factory = SerializationFactoryImpl(mutableMapOf()).apply { registerScheme(AMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap(128))) } - return object : SerializationEnvironmentImpl(factory, AMQP_P2P_CONTEXT) { - override fun toString() = "testSerializationEnv($label)" - } + return SerializationEnvironment.with(factory, AMQP_P2P_CONTEXT) } private class AMQPSerializationScheme( diff --git a/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt b/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt index d28362a34b..ddcd04be56 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt @@ -3,6 +3,7 @@ package net.corda.core.contracts import net.corda.core.KeepForDJVM import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty +import net.corda.core.serialization.SerializableCalculatedProperty import java.security.PublicKey /** @@ -38,6 +39,7 @@ interface FungibleAsset : OwnableState { * There must be an ExitCommand signed by these keys to destroy the amount. While all states require their * owner to sign, some (i.e. cash) also require the issuer. */ + @get:SerializableCalculatedProperty val exitKeys: Collection /** diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializable.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAnnotations.kt similarity index 76% rename from core/src/main/kotlin/net/corda/core/serialization/CordaSerializable.kt rename to core/src/main/kotlin/net/corda/core/serialization/SerializationAnnotations.kt index ce80444256..95fd23e60f 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializable.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAnnotations.kt @@ -19,4 +19,12 @@ import java.lang.annotation.Inherited @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @Inherited -annotation class CordaSerializable \ No newline at end of file +annotation class CordaSerializable + + +/** + * Used to annotate methods which expose calculated values that we want to be serialized for use by the class carpenter. + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION) +annotation class SerializableCalculatedProperty \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt index 448d1ab25f..6769b73b03 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/CheckpointSerializationAPI.kt @@ -13,75 +13,12 @@ import java.io.NotSerializableException object CheckpointSerializationDefaults { @DeleteForDJVM val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext - val CHECKPOINT_SERIALIZATION_FACTORY get() = effectiveSerializationEnv.checkpointSerializationFactory -} - -/** - * A class for serializing and deserializing objects at checkpoints, using Kryo serialization. - */ -@KeepForDJVM -class CheckpointSerializationFactory( - private val scheme: CheckpointSerializationScheme -) { - - val defaultContext: CheckpointSerializationContext get() = _currentContext.get() ?: effectiveSerializationEnv.checkpointContext - - private val creator: List = Exception().stackTrace.asList() - - /** - * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. - * - * @param byteSequence The bytes to deserialize, including a format header prefix. - * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. - * @param context A context that configures various parameters to deserialization. - */ - fun deserialize(byteSequence: ByteSequence, clazz: Class, context: CheckpointSerializationContext): T { - return withCurrentContext(context) { scheme.deserialize(byteSequence, clazz, context) } - } - - /** - * Serialize an object to bytes using the preferred serialization format version from the context. - * - * @param obj The object to be serialized. - * @param context A context that configures various parameters to serialization, including the serialization format version. - */ - fun serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes { - return withCurrentContext(context) { scheme.serialize(obj, context) } - } - - override fun toString(): String { - return "${this.javaClass.name} scheme=$scheme ${creator.joinToString("\n")}" - } - - override fun equals(other: Any?): Boolean { - return other is CheckpointSerializationFactory && other.scheme == this.scheme - } - - override fun hashCode(): Int = scheme.hashCode() - - private val _currentContext = ThreadLocal() - - /** - * Change the current context inside the block to that supplied. - */ - fun withCurrentContext(context: CheckpointSerializationContext?, block: () -> T): T { - val priorContext = _currentContext.get() - if (context != null) _currentContext.set(context) - try { - return block() - } finally { - if (context != null) _currentContext.set(priorContext) - } - } - - companion object { - val defaultFactory: CheckpointSerializationFactory get() = effectiveSerializationEnv.checkpointSerializationFactory - } + val CHECKPOINT_SERIALIZER get() = effectiveSerializationEnv.checkpointSerializer } @KeepForDJVM @DoNotImplement -interface CheckpointSerializationScheme { +interface CheckpointSerializer { @Throws(NotSerializableException::class) fun deserialize(byteSequence: ByteSequence, clazz: Class, context: CheckpointSerializationContext): T @@ -167,32 +104,36 @@ interface CheckpointSerializationContext { /* * Convenience extension method for deserializing a ByteSequence, utilising the default factory. */ -inline fun ByteSequence.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory, - context: CheckpointSerializationContext): T { - return serializationFactory.deserialize(this, T::class.java, context) +@JvmOverloads +inline fun ByteSequence.checkpointDeserialize( + context: CheckpointSerializationContext = effectiveSerializationEnv.checkpointContext): T { + return effectiveSerializationEnv.checkpointSerializer.deserialize(this, T::class.java, context) } /** * Convenience extension method for deserializing SerializedBytes with type matching, utilising the default factory. */ -inline fun SerializedBytes.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory, - context: CheckpointSerializationContext): T { - return serializationFactory.deserialize(this, T::class.java, context) +@JvmOverloads +inline fun SerializedBytes.checkpointDeserialize( + context: CheckpointSerializationContext = effectiveSerializationEnv.checkpointContext): T { + return effectiveSerializationEnv.checkpointSerializer.deserialize(this, T::class.java, context) } /** * Convenience extension method for deserializing a ByteArray, utilising the default factory. */ -inline fun ByteArray.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory, - context: CheckpointSerializationContext): T { +@JvmOverloads +inline fun ByteArray.checkpointDeserialize( + context: CheckpointSerializationContext = effectiveSerializationEnv.checkpointContext): T { require(isNotEmpty()) { "Empty bytes" } - return this.sequence().checkpointDeserialize(serializationFactory, context) + return this.sequence().checkpointDeserialize(context) } /** * Convenience extension method for serializing an object of type T, utilising the default factory. */ -fun T.checkpointSerialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory, - context: CheckpointSerializationContext): SerializedBytes { - return serializationFactory.serialize(this, context) +@JvmOverloads +fun T.checkpointSerialize( + context: CheckpointSerializationContext = effectiveSerializationEnv.checkpointContext): SerializedBytes { + return effectiveSerializationEnv.checkpointSerializer.serialize(this, context) } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt index 441cd52be4..b213b6322d 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt @@ -11,38 +11,63 @@ import net.corda.core.serialization.SerializationFactory @KeepForDJVM interface SerializationEnvironment { + + companion object { + fun with( + serializationFactory: SerializationFactory, + p2pContext: SerializationContext, + rpcServerContext: SerializationContext? = null, + rpcClientContext: SerializationContext? = null, + storageContext: SerializationContext? = null, + + checkpointContext: CheckpointSerializationContext? = null, + checkpointSerializer: CheckpointSerializer? = null + ): SerializationEnvironment = + SerializationEnvironmentImpl( + serializationFactory = serializationFactory, + p2pContext = p2pContext, + optionalRpcServerContext = rpcServerContext, + optionalRpcClientContext = rpcClientContext, + optionalStorageContext = storageContext, + optionalCheckpointContext = checkpointContext, + optionalCheckpointSerializer = checkpointSerializer + ) + } + val serializationFactory: SerializationFactory - val checkpointSerializationFactory: CheckpointSerializationFactory val p2pContext: SerializationContext val rpcServerContext: SerializationContext val rpcClientContext: SerializationContext val storageContext: SerializationContext + + val checkpointSerializer: CheckpointSerializer val checkpointContext: CheckpointSerializationContext } @KeepForDJVM -open class SerializationEnvironmentImpl( +private class SerializationEnvironmentImpl( override val serializationFactory: SerializationFactory, override val p2pContext: SerializationContext, - rpcServerContext: SerializationContext? = null, - rpcClientContext: SerializationContext? = null, - storageContext: SerializationContext? = null, - checkpointContext: CheckpointSerializationContext? = null, - checkpointSerializationFactory: CheckpointSerializationFactory? = null) : SerializationEnvironment { - // Those that are passed in as null are never inited: - override lateinit var rpcServerContext: SerializationContext - override lateinit var rpcClientContext: SerializationContext - override lateinit var storageContext: SerializationContext - override lateinit var checkpointContext: CheckpointSerializationContext - override lateinit var checkpointSerializationFactory: CheckpointSerializationFactory + private val optionalRpcServerContext: SerializationContext? = null, + private val optionalRpcClientContext: SerializationContext? = null, + private val optionalStorageContext: SerializationContext? = null, + private val optionalCheckpointContext: CheckpointSerializationContext? = null, + private val optionalCheckpointSerializer: CheckpointSerializer? = null) : SerializationEnvironment { - init { - rpcServerContext?.let { this.rpcServerContext = it } - rpcClientContext?.let { this.rpcClientContext = it } - storageContext?.let { this.storageContext = it } - checkpointContext?.let { this.checkpointContext = it } - checkpointSerializationFactory?.let { this.checkpointSerializationFactory = it } - } + override val rpcServerContext: SerializationContext get() = optionalRpcServerContext ?: + throw UnsupportedOperationException("RPC server serialization not supported in this environment") + + override val rpcClientContext: SerializationContext get() = optionalRpcClientContext ?: + throw UnsupportedOperationException("RPC client serialization not supported in this environment") + + override val storageContext: SerializationContext get() = optionalStorageContext ?: + throw UnsupportedOperationException("Storage serialization not supported in this environment") + + override val checkpointContext: CheckpointSerializationContext get() = optionalCheckpointContext ?: + throw UnsupportedOperationException("Checkpoint serialization not supported in this environment") + + override val checkpointSerializer: CheckpointSerializer get() = optionalCheckpointSerializer ?: + throw UnsupportedOperationException("Checkpoint serialization not supported in this environment") } private val _nodeSerializationEnv = SimpleToggleField("nodeSerializationEnv", true) diff --git a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt index 57a3f090b0..2b314a8772 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt @@ -29,7 +29,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si */ abstract val bytes: ByteArray - /** Returns a [ByteArrayInputStream] of the bytes */ + /** Returns a [ByteArrayInputStream] of the bytes. */ fun open() = ByteArrayInputStream(_bytes, offset, size) /** @@ -109,7 +109,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si return Integer.signum(unsignedThis - unsignedOther) } } - // First min bytes is the same, so now resort to size + // First min bytes is the same, so now resort to size. return Integer.signum(this.size - other.size) } @@ -191,12 +191,12 @@ fun ByteArray.toHexString(): String = DatatypeConverter.printHexBinary(this) fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this) /** - * Class is public for serialization purposes + * Class is public for serialization purposes. */ @KeepForDJVM class OpaqueBytesSubSequence(override val bytes: ByteArray, offset: Int, size: Int) : ByteSequence(bytes, offset, size) { init { require(offset >= 0 && offset < bytes.size) - require(size >= 0 && size <= bytes.size) + require(size >= 0 && offset + size <= bytes.size) } } diff --git a/core/src/test/java/net/corda/core/flows/SerializationApiInJavaTest.java b/core/src/test/java/net/corda/core/flows/SerializationApiInJavaTest.java index 55e66c1766..9f48a7ba0a 100644 --- a/core/src/test/java/net/corda/core/flows/SerializationApiInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/SerializationApiInJavaTest.java @@ -1,7 +1,5 @@ package net.corda.core.flows; -import net.corda.core.serialization.internal.CheckpointSerializationDefaults; -import net.corda.core.serialization.internal.CheckpointSerializationFactory; import net.corda.core.serialization.SerializationDefaults; import net.corda.core.serialization.SerializationFactory; import net.corda.testing.core.SerializationEnvironmentRule; @@ -32,12 +30,10 @@ public class SerializationApiInJavaTest { SerializationDefaults defaults = SerializationDefaults.INSTANCE; SerializationFactory factory = defaults.getSERIALIZATION_FACTORY(); - CheckpointSerializationDefaults checkpointDefaults = CheckpointSerializationDefaults.INSTANCE; - CheckpointSerializationFactory checkpointSerializationFactory = checkpointDefaults.getCHECKPOINT_SERIALIZATION_FACTORY(); serialize("hello", factory, defaults.getP2P_CONTEXT()); serialize("hello", factory, defaults.getRPC_SERVER_CONTEXT()); serialize("hello", factory, defaults.getRPC_CLIENT_CONTEXT()); serialize("hello", factory, defaults.getSTORAGE_CONTEXT()); - checkpointSerialize("hello", checkpointSerializationFactory, checkpointDefaults.getCHECKPOINT_CONTEXT()); + checkpointSerialize("hello"); } } diff --git a/docs/source/serialization.rst b/docs/source/serialization.rst index 290e11c74b..878179f985 100644 --- a/docs/source/serialization.rst +++ b/docs/source/serialization.rst @@ -560,10 +560,33 @@ be able to use reflection over the deserialized data, for scripting languages th ensuring classes not on the classpath can be deserialized without loading potentially malicious code. If the original class implements some interfaces then the carpenter will make sure that all of the interface methods are -backed by feilds. If that's not the case then an exception will be thrown during deserialization. This check can +backed by fields. If that's not the case then an exception will be thrown during deserialization. This check can be turned off with ``SerializationContext.withLenientCarpenter``. This can be useful if only the field getters are needed, say in an object viewer. +Calculated values +````````````````` + +In some cases, for example the `exitKeys` field in ``FungibleState``, a property in an interface may normally be implemented +as a *calculated* value, with a "getter" method for reading it but neither a corresponding constructor parameter nor a +"setter" method for writing it. In this case, it will not automatically be included among the properties to be serialized, +since the receiving class would ordinarily be able to re-calculate it on demand. However, a synthesized class will not +have the method implementation which knows how to calculate the value, and a cast to the interface will fail because the +property is not serialized and so the "getter" method present in the interface will not be synthesized. + +The solution is to annotate the method with the ``SerializableCalculatedProperty`` annotation, which will cause the value +exposed by the method to be read and transmitted during serialization, but discarded during normal deserialization. The +synthesized class will then include a backing field together with a "getter" for the serialized calculated value, and will +remain compatible with the interface. + +If the annotation is added to the method in the *interface*, then all implementing classes must calculate the value and +none may have a corresponding backing field; alternatively, it can be added to the overriding method on each implementing +class where the value is calculated and there is no backing field. If the field is a Kotlin ``val``, then the annotation +should be targeted at its getter method, e.g. ``@get:SerializableCalculatedProperty``. + +Future enhancements +``````````````````` + Possible future enhancements include: #. Java singleton support. We will add support for identifying classes which are singletons and identifying the diff --git a/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt b/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt index 9d5879a5ce..6045f1001e 100644 --- a/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt +++ b/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorkerServiceHub.kt @@ -22,10 +22,7 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.services.CordaService import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.internal.CheckpointSerializationFactory -import net.corda.core.serialization.internal.SerializationEnvironmentImpl -import net.corda.core.serialization.internal.effectiveSerializationEnv -import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.serialization.internal.* import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.CordaClock @@ -38,7 +35,6 @@ import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT -import net.corda.node.serialization.kryo.KryoSerializationScheme import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler @@ -360,12 +356,11 @@ class FlowWorkerServiceHub(override val configuration: NodeConfiguration, overri } if (!serializationExists) { val classloader = cordappLoader.appClassLoader - nodeSerializationEnv = SerializationEnvironmentImpl( + nodeSerializationEnv = SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps)) registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps)) }, - checkpointSerializationFactory = CheckpointSerializationFactory(KryoSerializationScheme), p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader), storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader), diff --git a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt index 05a4927c9a..71d1022555 100644 --- a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt +++ b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorkerServiceHub.kt @@ -14,10 +14,7 @@ import net.corda.core.node.services.ContractUpgradeService import net.corda.core.node.services.TransactionVerifierService import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.internal.CheckpointSerializationFactory -import net.corda.core.serialization.internal.SerializationEnvironmentImpl -import net.corda.core.serialization.internal.effectiveSerializationEnv -import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.serialization.internal.* import net.corda.core.utilities.contextLogger import net.corda.node.CordaClock import net.corda.node.SimpleClock @@ -28,7 +25,6 @@ import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT -import net.corda.node.serialization.kryo.KryoSerializationScheme import net.corda.node.services.api.AuditService import net.corda.node.services.api.MonitoringService import net.corda.node.services.api.ServiceHubInternal @@ -195,12 +191,11 @@ class RpcWorkerServiceHub(override val configuration: NodeConfiguration, overrid } if (!serializationExists) { val classloader = cordappLoader.appClassLoader - nodeSerializationEnv = SerializationEnvironmentImpl( + nodeSerializationEnv = SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps)) registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps)) }, - checkpointSerializationFactory = CheckpointSerializationFactory(KryoSerializationScheme), p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader), storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader), diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt index e95609abb2..34e47a890f 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt @@ -16,6 +16,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState +import net.corda.core.serialization.SerializableCalculatedProperty import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index 49d0704c84..1747cc2333 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -14,7 +14,7 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow @@ -393,7 +393,7 @@ internal constructor(private val initSerEnv: Boolean, // We need to to set serialization env, because generation of parameters is run from Cordform. private fun initialiseSerialization() { - _contextSerializationEnv.set(SerializationEnvironmentImpl( + _contextSerializationEnv.set(SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPParametersSerializationScheme) }, diff --git a/node/src/main/kotlin/net/corda/node/internal/CheckpointVerifier.kt b/node/src/main/kotlin/net/corda/node/internal/CheckpointVerifier.kt index 53f22b3147..48b8c71971 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CheckpointVerifier.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CheckpointVerifier.kt @@ -4,7 +4,6 @@ import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.node.ServiceHub -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.internal.CheckpointSerializationDefaults import net.corda.core.serialization.internal.checkpointDeserialize import net.corda.node.services.api.CheckpointStorage @@ -21,7 +20,7 @@ object CheckpointVerifier { */ fun verifyCheckpointsCompatible(checkpointStorage: CheckpointStorage, currentCordapps: List, platformVersion: Int, serviceHub: ServiceHub, tokenizableServices: List) { val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext( - CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub) + CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZER, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub) ) checkpointStorage.getAllCheckpoints().forEach { (_, serializedCheckpoint) -> diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index af778c0ccc..df8807330f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -21,8 +21,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub -import net.corda.core.serialization.internal.CheckpointSerializationFactory -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger @@ -38,7 +37,7 @@ import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT -import net.corda.node.serialization.kryo.KryoSerializationScheme +import net.corda.node.serialization.kryo.KryoCheckpointSerializer import net.corda.node.services.Permissions import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal @@ -495,17 +494,19 @@ open class Node(configuration: NodeConfiguration, private fun initialiseSerialization() { if (!initialiseSerialization) return val classloader = cordappLoader.appClassLoader - nodeSerializationEnv = SerializationEnvironmentImpl( + nodeSerializationEnv = SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps)) registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps)) }, - checkpointSerializationFactory = CheckpointSerializationFactory(KryoSerializationScheme), p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader), + rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null, //even Shell embeded in the node connects via RPC to the node storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader), - checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader), - rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null) //even Shell embeded in the node connects via RPC to the node + + checkpointSerializer = KryoCheckpointSerializer, + checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader) + ) } /** Starts a blocking event loop for message dispatch. */ diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/KryoSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/KryoCheckpointSerializer.kt similarity index 97% rename from node/src/main/kotlin/net/corda/node/serialization/kryo/KryoSerializationScheme.kt rename to node/src/main/kotlin/net/corda/node/serialization/kryo/KryoCheckpointSerializer.kt index 3b56e8529b..ce6ffe46ef 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/KryoSerializationScheme.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/KryoCheckpointSerializer.kt @@ -12,7 +12,7 @@ import com.esotericsoftware.kryo.serializers.ClosureSerializer import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.* import net.corda.core.serialization.internal.CheckpointSerializationContext -import net.corda.core.serialization.internal.CheckpointSerializationScheme +import net.corda.core.serialization.internal.CheckpointSerializer import net.corda.core.utilities.ByteSequence import net.corda.serialization.internal.* import net.corda.serialization.internal.CordaSerializationEncoding.SNAPPY @@ -32,7 +32,7 @@ private object AutoCloseableSerialisationDetector : Serializer() override fun read(kryo: Kryo, input: Input, type: Class) = throw IllegalStateException("Should not reach here!") } -object KryoSerializationScheme : CheckpointSerializationScheme { +object KryoCheckpointSerializer : CheckpointSerializer { private val kryoPoolsForContexts = ConcurrentHashMap, KryoPool>() private fun getPool(context: CheckpointSerializationContext): KryoPool { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/MultiThreadedStateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/MultiThreadedStateMachineManager.kt index 67e7e977b9..35ce810d8a 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/MultiThreadedStateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/MultiThreadedStateMachineManager.kt @@ -139,7 +139,7 @@ class MultiThreadedStateMachineManager( checkQuasarJavaAgentPresence() this.tokenizableServices = tokenizableServices val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext( - CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub) + CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZER, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub) ) this.checkpointSerializationContext = checkpointSerializationContext this.actionExecutor = makeActionExecutor(checkpointSerializationContext) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt index a47799e0aa..60fb528bcb 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt @@ -127,7 +127,7 @@ class SingleThreadedStateMachineManager( override fun start(tokenizableServices: List) { checkQuasarJavaAgentPresence() val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext( - CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub) + CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZER, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub) ) this.checkpointSerializationContext = checkpointSerializationContext this.actionExecutor = makeActionExecutor(checkpointSerializationContext) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt index 72fa52bdc1..c35ae146ab 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt @@ -22,7 +22,7 @@ import net.corda.core.internal.notary.isConsumedByTheSameTx import net.corda.core.internal.notary.validateTimeWindow import net.corda.core.serialization.* import net.corda.core.serialization.internal.CheckpointSerializationDefaults -import net.corda.core.serialization.internal.CheckpointSerializationFactory + import net.corda.core.serialization.internal.checkpointSerialize import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.contextLogger @@ -201,7 +201,7 @@ class RaftTransactionCommitLog( class CordaKryoSerializer : TypeSerializer { private val context = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withEncoding(CordaSerializationEncoding.SNAPPY) - private val factory = CheckpointSerializationFactory.defaultFactory + private val checkpointSerializer = CheckpointSerializationDefaults.CHECKPOINT_SERIALIZER override fun write(obj: T, buffer: BufferOutput<*>, serializer: Serializer) { val serialized = obj.checkpointSerialize(context = context) @@ -213,7 +213,7 @@ class RaftTransactionCommitLog( val size = buffer.readInt() val serialized = ByteArray(size) buffer.read(serialized) - return factory.deserialize(ByteSequence.of(serialized), type, context) + return checkpointSerializer.deserialize(ByteSequence.of(serialized), type, context) } } } diff --git a/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt b/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt index 5598f38f67..abfec25bc2 100644 --- a/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt +++ b/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt @@ -13,7 +13,6 @@ import net.corda.core.crypto.* import net.corda.core.internal.FetchDataFlow import net.corda.core.serialization.* import net.corda.core.serialization.internal.CheckpointSerializationContext -import net.corda.core.serialization.internal.CheckpointSerializationFactory import net.corda.core.serialization.internal.checkpointDeserialize import net.corda.core.serialization.internal.checkpointSerialize import net.corda.core.utilities.ByteSequence @@ -23,11 +22,13 @@ import net.corda.node.services.persistence.NodeAttachmentService import net.corda.serialization.internal.* import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.TestIdentity +import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule import net.corda.testing.internal.rigorousMock import org.assertj.core.api.Assertions.* import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -48,12 +49,12 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { fun compression() = arrayOf(null) + CordaSerializationEncoding.values() } - private lateinit var factory: CheckpointSerializationFactory + @get:Rule + val serializationRule = CheckpointSerializationEnvironmentRule() private lateinit var context: CheckpointSerializationContext @Before fun setup() { - factory = CheckpointSerializationFactory(KryoSerializationScheme) context = CheckpointSerializationContextImpl( javaClass.classLoader, AllWhitelist, @@ -69,15 +70,15 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { fun `simple data class`() { val birthday = Instant.parse("1984-04-17T00:30:00.00Z") val mike = Person("mike", birthday) - val bits = mike.checkpointSerialize(factory, context) - assertThat(bits.checkpointDeserialize(factory, context)).isEqualTo(Person("mike", birthday)) + val bits = mike.checkpointSerialize(context) + assertThat(bits.checkpointDeserialize(context)).isEqualTo(Person("mike", birthday)) } @Test fun `null values`() { val bob = Person("bob", null) - val bits = bob.checkpointSerialize(factory, context) - assertThat(bits.checkpointDeserialize(factory, context)).isEqualTo(Person("bob", null)) + val bits = bob.checkpointSerialize(context) + assertThat(bits.checkpointDeserialize(context)).isEqualTo(Person("bob", null)) } @Test @@ -85,10 +86,10 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { val noReferencesContext = context.withoutReferences() val obj : ByteSequence = Ints.toByteArray(0x01234567).sequence() val originalList : ArrayList = ArrayList().apply { this += obj } - val deserialisedList = originalList.checkpointSerialize(factory, noReferencesContext).checkpointDeserialize(factory, noReferencesContext) + val deserialisedList = originalList.checkpointSerialize(noReferencesContext).checkpointDeserialize(noReferencesContext) originalList += obj deserialisedList += obj - assertThat(deserialisedList.checkpointSerialize(factory, noReferencesContext)).isEqualTo(originalList.checkpointSerialize(factory, noReferencesContext)) + assertThat(deserialisedList.checkpointSerialize(noReferencesContext)).isEqualTo(originalList.checkpointSerialize(noReferencesContext)) } @Test @@ -105,14 +106,14 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { this += instant this += instant } - assertThat(listWithSameInstances.checkpointSerialize(factory, noReferencesContext)).isEqualTo(listWithCopies.checkpointSerialize(factory, noReferencesContext)) + assertThat(listWithSameInstances.checkpointSerialize(noReferencesContext)).isEqualTo(listWithCopies.checkpointSerialize(noReferencesContext)) } @Test fun `cyclic object graph`() { val cyclic = Cyclic(3) - val bits = cyclic.checkpointSerialize(factory, context) - assertThat(bits.checkpointDeserialize(factory, context)).isEqualTo(cyclic) + val bits = cyclic.checkpointSerialize(context) + assertThat(bits.checkpointDeserialize(context)).isEqualTo(cyclic) } @Test @@ -124,7 +125,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { signature.verify(bitsToSign) assertThatThrownBy { signature.verify(wrongBits) } - val deserialisedKeyPair = keyPair.checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val deserialisedKeyPair = keyPair.checkpointSerialize(context).checkpointDeserialize(context) val deserialisedSignature = deserialisedKeyPair.sign(bitsToSign) deserialisedSignature.verify(bitsToSign) assertThatThrownBy { deserialisedSignature.verify(wrongBits) } @@ -132,28 +133,28 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `write and read Kotlin object singleton`() { - val serialised = TestSingleton.checkpointSerialize(factory, context) - val deserialised = serialised.checkpointDeserialize(factory, context) + val serialised = TestSingleton.checkpointSerialize(context) + val deserialised = serialised.checkpointDeserialize(context) assertThat(deserialised).isSameAs(TestSingleton) } @Test fun `check Kotlin EmptyList can be serialised`() { - val deserialisedList: List = emptyList().checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val deserialisedList: List = emptyList().checkpointSerialize(context).checkpointDeserialize(context) assertEquals(0, deserialisedList.size) assertEquals(Collections.emptyList().javaClass, deserialisedList.javaClass) } @Test fun `check Kotlin EmptySet can be serialised`() { - val deserialisedSet: Set = emptySet().checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val deserialisedSet: Set = emptySet().checkpointSerialize(context).checkpointDeserialize(context) assertEquals(0, deserialisedSet.size) assertEquals(Collections.emptySet().javaClass, deserialisedSet.javaClass) } @Test fun `check Kotlin EmptyMap can be serialised`() { - val deserialisedMap: Map = emptyMap().checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val deserialisedMap: Map = emptyMap().checkpointSerialize(context).checkpointDeserialize(context) assertEquals(0, deserialisedMap.size) assertEquals(Collections.emptyMap().javaClass, deserialisedMap.javaClass) } @@ -161,7 +162,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `InputStream serialisation`() { val rubbish = ByteArray(12345) { (it * it * 0.12345).toByte() } - val readRubbishStream: InputStream = rubbish.inputStream().checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val readRubbishStream: InputStream = rubbish.inputStream().checkpointSerialize(context).checkpointDeserialize(context) for (i in 0..12344) { assertEquals(rubbish[i], readRubbishStream.read().toByte()) } @@ -171,7 +172,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `InputStream serialisation does not write trailing garbage`() { val byteArrays = listOf("123", "456").map { it.toByteArray() } - val streams = byteArrays.map { it.inputStream() }.checkpointSerialize(factory, context).checkpointDeserialize(factory, context).iterator() + val streams = byteArrays.map { it.inputStream() }.checkpointSerialize(context).checkpointDeserialize(context).iterator() byteArrays.forEach { assertArrayEquals(it, streams.next().readBytes()) } assertFalse(streams.hasNext()) } @@ -182,8 +183,8 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { val testBytes = testString.toByteArray() val meta = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)) - val serializedMetaData = meta.checkpointSerialize(factory, context).bytes - val meta2 = serializedMetaData.checkpointDeserialize(factory, context) + val serializedMetaData = meta.checkpointSerialize(context).bytes + val meta2 = serializedMetaData.checkpointDeserialize(context) assertEquals(meta2, meta) } @@ -191,7 +192,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { fun `serialize - deserialize Logger`() { val storageContext: CheckpointSerializationContext = context val logger = LoggerFactory.getLogger("aName") - val logger2 = logger.checkpointSerialize(factory, storageContext).checkpointDeserialize(factory, storageContext) + val logger2 = logger.checkpointSerialize(storageContext).checkpointDeserialize(storageContext) assertEquals(logger.name, logger2.name) assertTrue(logger === logger2) } @@ -203,7 +204,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { SecureHash.sha256(rubbish), rubbish.size, rubbish.inputStream() - ).checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + ).checkpointSerialize(context).checkpointDeserialize(context) for (i in 0..12344) { assertEquals(rubbish[i], readRubbishStream.read().toByte()) } @@ -230,8 +231,8 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 )) - val serializedBytes = expected.checkpointSerialize(factory, context) - val actual = serializedBytes.checkpointDeserialize(factory, context) + val serializedBytes = expected.checkpointSerialize(context) + val actual = serializedBytes.checkpointDeserialize(context) assertEquals(expected, actual) } @@ -278,14 +279,13 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { } } Tmp() - val factory = CheckpointSerializationFactory(KryoSerializationScheme) val context = CheckpointSerializationContextImpl( javaClass.classLoader, AllWhitelist, emptyMap(), true, null) - pt.checkpointSerialize(factory, context) + pt.checkpointSerialize(context) } @Test @@ -293,7 +293,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { val exception = IllegalArgumentException("fooBar") val toBeSuppressedOnSenderSide = IllegalStateException("bazz1") exception.addSuppressed(toBeSuppressedOnSenderSide) - val exception2 = exception.checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val exception2 = exception.checkpointSerialize(context).checkpointDeserialize(context) assertEquals(exception.message, exception2.message) assertEquals(1, exception2.suppressed.size) @@ -308,7 +308,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `serialize - deserialize Exception no suppressed`() { val exception = IllegalArgumentException("fooBar") - val exception2 = exception.checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val exception2 = exception.checkpointSerialize(context).checkpointDeserialize(context) assertEquals(exception.message, exception2.message) assertEquals(0, exception2.suppressed.size) @@ -322,7 +322,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { fun `serialize - deserialize HashNotFound`() { val randomHash = SecureHash.randomSHA256() val exception = FetchDataFlow.HashNotFound(randomHash) - val exception2 = exception.checkpointSerialize(factory, context).checkpointDeserialize(factory, context) + val exception2 = exception.checkpointSerialize(context).checkpointDeserialize(context) assertEquals(randomHash, exception2.requested) } @@ -330,17 +330,17 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { fun `compression has the desired effect`() { compression ?: return val data = ByteArray(12345).also { Random(0).nextBytes(it) }.let { it + it } - val compressed = data.checkpointSerialize(factory, context) + val compressed = data.checkpointSerialize(context) assertEquals(.5, compressed.size.toDouble() / data.size, .03) - assertArrayEquals(data, compressed.checkpointDeserialize(factory, context)) + assertArrayEquals(data, compressed.checkpointDeserialize(context)) } @Test fun `a particular encoding can be banned for deserialization`() { compression ?: return doReturn(false).whenever(context.encodingWhitelist).acceptEncoding(compression) - val compressed = "whatever".checkpointSerialize(factory, context) - catchThrowable { compressed.checkpointDeserialize(factory, context) }.run { + val compressed = "whatever".checkpointSerialize(context) + catchThrowable { compressed.checkpointDeserialize(context) }.run { assertSame(KryoException::class.java, javaClass) assertEquals(encodingNotPermittedFormat.format(compression), message) } @@ -351,8 +351,8 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { class Holder(val holder: ByteArray) val obj = Holder(ByteArray(20000)) - val uncompressedSize = obj.checkpointSerialize(factory, context.withEncoding(null)).size - val compressedSize = obj.checkpointSerialize(factory, context.withEncoding(CordaSerializationEncoding.SNAPPY)).size + val uncompressedSize = obj.checkpointSerialize(context.withEncoding(null)).size + val compressedSize = obj.checkpointSerialize(context.withEncoding(CordaSerializationEncoding.SNAPPY)).size // If these need fixing, sounds like Kryo wire format changed and checkpoints might not surive an upgrade. assertEquals(20222, uncompressedSize) assertEquals(1111, compressedSize) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index 6784d43362..93e60859fe 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -34,6 +34,7 @@ import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.startFlow import org.junit.After import org.junit.Test +import java.security.PublicKey import java.util.* import java.util.concurrent.atomic.AtomicBoolean import kotlin.reflect.jvm.jvmName @@ -127,7 +128,7 @@ class VaultSoftLockManagerTest { override val owner get() = participants[0] override fun withNewOwner(newOwner: AbstractParty) = throw UnsupportedOperationException() override val amount get() = Amount(1, Issued(PartyAndReference(owner, OpaqueBytes.of(1)), Unit)) - override val exitKeys get() = throw UnsupportedOperationException() + override val exitKeys get() = emptyList() override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty) = throw UnsupportedOperationException() override fun equals(other: Any?) = other is FungibleAssetImpl && participants == other.participants override fun hashCode() = participants.hashCode() diff --git a/release-tools/testing/args.py b/release-tools/testing/args.py index 9eb92a8a59..58ed3f0b90 100644 --- a/release-tools/testing/args.py +++ b/release-tools/testing/args.py @@ -5,7 +5,7 @@ import sys, traceback # {{{ Representation of a command-line program class Program: - # Create a new command-line program represenation, provided an optional name and description + # Create a new command-line program representation, provided an optional name and description def __init__(self, name=None, description=None): self.parser = ArgumentParser(name, description=description) self.subparsers = self.parser.add_subparsers(title='commands') @@ -39,7 +39,10 @@ class Program: t, exception, tb = sys.exc_info() self.parser.error('{}\n\n{}'.format(error.message, '\n'.join(traceback.format_tb(tb)))) else: - self.parser.error(error.message) + try: + self.parser.error(error.message) + except AttributeError: + self.parser.error(str(error)) # }}} # {{{ Representation of a sub-command of a command-line program diff --git a/release-tools/testing/jira_manager.py b/release-tools/testing/jira_manager.py index af57865398..5f9e66fa60 100644 --- a/release-tools/testing/jira_manager.py +++ b/release-tools/testing/jira_manager.py @@ -3,6 +3,13 @@ from jira import JIRA from jira.exceptions import JIRAError # }}} +# {{{ Python 2 and 3 interoperability +try: + unicode('') +except NameError: + unicode = str +# }}} + # {{{ Class for interacting with a hosted JIRA system class Jira: diff --git a/release-tools/testing/test-manager b/release-tools/testing/test-manager index 96bfd97a36..bfea84eb68 100755 --- a/release-tools/testing/test-manager +++ b/release-tools/testing/test-manager @@ -36,7 +36,8 @@ except: product_map = { 'OS' : 'Corda', 'ENT' : 'Corda Enterprise', - 'NS' : 'Corda Network Services', + 'NS' : 'ENM', + 'ENM' : 'ENM', 'TEST' : 'Corda', # for demo and test purposes } # }}} @@ -235,10 +236,15 @@ def create_release_candidate(args): print() has_tests = False for issue in jira.search(QUERY_LIST_TEST_INSTANCES, args.PRODUCT, version): - print(u' - {} {}'.format(blue(issue.key), issue.fields.summary)) + test_status = issue.fields.status['name'] + print(u' - {} {} ({})'.format(blue(issue.key), issue.fields.summary, test_status)) epic_field = jira.custom_fields_by_name['Epic Link'] epic = issue.fields[epic_field] if epic_field in issue.fields.to_dict() else '' labels = issue.fields.labels + [epic] + if test_status in ['Pass', 'Fail', 'Descope']: + print(u' {} - Parent test is marked as {}'.format(yellow('SKIPPED'), test_status)) + print() + continue print() has_tests = True print(u' - Creating test run ticket for release candidate {} ...'.format(yellow('RC{:02d}'.format(CANDIDATE)))) @@ -278,7 +284,7 @@ def main(): def mixin_version_and_product(command): mixin_product(command) - command.add('VERSION', help='the target version of the release, e.g., 4.0', type=float) + command.add('VERSION', help='the target version of the release, e.g., 4.0', type=str) def mixin_candidate(command, optional=False): if optional: diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt index 785ce47597..025e27a38a 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt @@ -5,7 +5,7 @@ import net.corda.core.DeleteForDJVM import net.corda.core.node.ServiceHub import net.corda.core.serialization.* import net.corda.core.serialization.internal.CheckpointSerializationContext -import net.corda.core.serialization.internal.CheckpointSerializationFactory +import net.corda.core.serialization.internal.CheckpointSerializer val serializationContextKey = SerializeAsTokenContext::class.java @@ -70,8 +70,8 @@ class SerializeAsTokenContextImpl(override val serviceHub: ServiceHub, init: Ser */ @DeleteForDJVM class CheckpointSerializeAsTokenContextImpl(override val serviceHub: ServiceHub, init: SerializeAsTokenContext.() -> Unit) : SerializeAsTokenContext { - constructor(toBeTokenized: Any, serializationFactory: CheckpointSerializationFactory, context: CheckpointSerializationContext, serviceHub: ServiceHub) : this(serviceHub, { - serializationFactory.serialize(toBeTokenized, context.withTokenContext(this)) + constructor(toBeTokenized: Any, serializer: CheckpointSerializer, context: CheckpointSerializationContext, serviceHub: ServiceHub) : this(serviceHub, { + serializer.serialize(toBeTokenized, context.withTokenContext(this)) }) private val classNameToSingleton = mutableMapOf() diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt index b9c50f7250..0cea2003f7 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt @@ -117,7 +117,7 @@ class DeserializationInput constructor( des { val envelope = getEnvelope(bytes, context.encodingWhitelist) - logger.trace("deserialize blob scheme=\"${envelope.schema.toString()}\"") + logger.trace("deserialize blob scheme=\"${envelope.schema}\"") clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz, context)) @@ -149,7 +149,7 @@ class DeserializationInput constructor( if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. val objectIndex = (obj.described as UnsignedInteger).toInt() - if (objectIndex !in 0..objectHistory.size) + if (objectIndex >= objectHistory.size) throw AMQPNotSerializableException( type, "Retrieval of existing reference failed. Requested index $objectIndex " + diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt index 79a3f85c00..4604af4505 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt @@ -125,7 +125,7 @@ abstract class EvolutionSerializer( // any particular NonNullable annotation type to indicate cross // compiler nullability val isKotlin = (new.type.javaClass.declaredAnnotations.any { - it.annotationClass.qualifiedName == "kotlin.Metadata" + it.annotationClass.qualifiedName == "kotlin.Metadata" }) constructor.parameters.withIndex().forEach { @@ -270,8 +270,8 @@ class EvolutionSerializerViaSetters( * 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( +interface EvolutionSerializerProvider { + fun getEvolutionSerializer( factory: SerializerFactory, typeNotation: TypeNotation, newSerializer: AMQPSerializer, @@ -283,12 +283,12 @@ abstract class EvolutionSerializerGetterBase { * between the received schema and the class as it exists now on the class path, */ @KeepForDJVM -class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { +object DefaultEvolutionSerializerProvider : EvolutionSerializerProvider { override fun getEvolutionSerializer(factory: SerializerFactory, typeNotation: TypeNotation, newSerializer: AMQPSerializer, schemas: SerializationSchemas): AMQPSerializer { - return factory.serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { + return factory.registerByDescriptor(typeNotation.descriptor.name!!) { when (typeNotation) { is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory) is RestrictedType -> { @@ -310,5 +310,4 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { } } } -} - +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt index dc142fce16..9ae529b608 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt @@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp import net.corda.core.internal.isConcreteClass import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.serialization.internal.amqp.SerializerFactory.Companion.nameForType @@ -61,7 +62,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS context: SerializationContext, debugIndent: Int) = ifThrowsAppend({ clazz.typeName } ) { - if (propertySerializers.size != javaConstructor?.parameterCount && + if (propertySerializers.deserializableSize != javaConstructor?.parameterCount && javaConstructor?.parameterCount ?: 0 > 0 ) { throw AMQPNotSerializableException(type, "Serialization constructor for class $type expects " @@ -86,8 +87,9 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS input: DeserializationInput, context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) { if (obj is List<*>) { - if (obj.size > propertySerializers.size) { - throw AMQPNotSerializableException(type, "Too many properties in described type $typeName") + if (obj.size != propertySerializers.size) { + throw AMQPNotSerializableException(type, "${obj.size} objects to deserialize, but " + + "${propertySerializers.size} properties in described type $typeName") } return if (propertySerializers.byConstructor) { @@ -109,8 +111,19 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS return construct(propertySerializers.serializationOrder .zip(obj) - .map { Pair(it.first.initialPosition, it.first.serializer.readProperty(it.second, schemas, input, context)) } - .sortedWith(compareBy({ it.first })) + .mapNotNull { (accessor, obj) -> + // Ensure values get read out of input no matter what + val value = accessor.serializer.readProperty(obj, schemas, input, context) + + when(accessor) { + is PropertyAccessorConstructor -> accessor.initialPosition to value + is CalculatedPropertyAccessor -> null + else -> throw UnsupportedOperationException( + "${accessor::class.simpleName} accessor not supported " + + "for constructor-based object building") + } + } + .sortedWith(compareBy { it.first }) .map { it.second }) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertyDescriptor.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertyDescriptor.kt index 882f067eba..3de78db04e 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertyDescriptor.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertyDescriptor.kt @@ -3,6 +3,7 @@ package net.corda.serialization.internal.amqp import com.google.common.reflect.TypeToken import net.corda.core.KeepForDJVM import net.corda.core.internal.isPublic +import net.corda.core.serialization.SerializableCalculatedProperty import net.corda.serialization.internal.amqp.MethodClassifier.* import java.lang.reflect.Field import java.lang.reflect.Method @@ -84,7 +85,7 @@ private val propertyMethodRegex = Regex("(?get|set|is)(?\\p{Lu}.*)") * take a single parameter of a type compatible with exampleProperty and isExampleProperty must * return a boolean */ -fun Class.propertyDescriptors(): Map { +internal fun Class.propertyDescriptors(): Map { val fieldProperties = superclassChain().declaredFields().byFieldName() return superclassChain().declaredMethods() @@ -96,9 +97,23 @@ fun Class.propertyDescriptors(): Map { .validated() } +/** + * Obtain [PropertyDescriptor]s for those calculated properties of a class which are annotated with + * [SerializableCalculatedProperty] + */ +internal fun Class.calculatedPropertyDescriptors(): Map = + superclassChain().withInterfaces().declaredMethods() + .thatArePublic() + .thatAreCalculated() + .toCalculatedProperties() + // Generate the sequence of classes starting with this class and ascending through it superclasses. private fun Class<*>.superclassChain() = generateSequence(this, Class<*>::getSuperclass) +private fun Sequence>.withInterfaces() = flatMap { + sequenceOf(it) + it.genericInterfaces.asSequence().map { it.asClass() } +} + // Obtain the fields declared by all classes in this sequence of classes. private fun Sequence>.declaredFields() = flatMap { it.declaredFields.asSequence() } @@ -108,18 +123,45 @@ private fun Sequence>.declaredMethods() = flatMap { it.declaredMethods. // Map a sequence of fields by field name. private fun Sequence.byFieldName() = map { it.name to it }.toMap() -// Select only those methods that are public (and are not the "getClass" method) +// Select only those methods that are public (and are not the "getClass" method). private fun Sequence.thatArePublic() = filter { it.isPublic && it.name != "getClass" } +// Select only those methods that are annotated with [SerializableCalculatedProperty]. +private fun Sequence.thatAreCalculated() = filter { + it.isAnnotationPresent(SerializableCalculatedProperty::class.java) +} + +// Convert a sequence of calculated property methods to a map of property descriptors by property name. +private fun Sequence.toCalculatedProperties(): Map { + val methodsByName = mutableMapOf() + for (method in this) { + val propertyNamedMethod = getPropertyNamedMethod(method) + ?: throw IllegalArgumentException("Calculated property method must have a name beginning with 'get' or 'is'") + + require(propertyNamedMethod.hasValidSignature()) { + "Calculated property name must have no parameters, and a non-void return type" + } + + val propertyName = propertyNamedMethod.fieldName.decapitalize() + methodsByName.compute(propertyName) { _, existingMethod -> + if (existingMethod == null) method + else leastGenericBy({ genericReturnType }, existingMethod, method) + } + } + return methodsByName.mapValues { (_, method) -> PropertyDescriptor(null, null, method) } +} + // Select only those methods that are isX/getX/setX methods -private fun Sequence.thatArePropertyMethods() = map { method -> - propertyMethodRegex.find(method.name)?.let { result -> +private fun Sequence.thatArePropertyMethods() = mapNotNull(::getPropertyNamedMethod) + +private fun getPropertyNamedMethod(method: Method): PropertyNamedMethod? { + return propertyMethodRegex.find(method.name)?.let { result -> PropertyNamedMethod( result.groups[2]!!.value, MethodClassifier.valueOf(result.groups[1]!!.value.toUpperCase()), method) } -}.filterNotNull() +} // Pick only those methods whose signatures are valid, discarding the remainder without warning. private fun Sequence.withValidSignature() = filter { it.hasValidSignature() } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt index c56071a84e..65a8998a14 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.amqp import net.corda.core.KeepForDJVM +import net.corda.core.serialization.SerializableCalculatedProperty import net.corda.core.utilities.loggerFor import java.io.NotSerializableException import java.lang.reflect.Field @@ -19,9 +20,9 @@ abstract class PropertyReader { * Accessor for those properties of a class that have defined getter functions. */ @KeepForDJVM -class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() { +class PublicPropertyReader(private val readMethod: Method) : PropertyReader() { init { - readMethod?.isAccessible = true + readMethod.isAccessible = true } private fun Method.returnsNullable(): Boolean { @@ -47,10 +48,10 @@ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() { } override fun read(obj: Any?): Any? { - return readMethod!!.invoke(obj) + return readMethod.invoke(obj) } - override fun isNullable(): Boolean = readMethod?.returnsNullable() ?: false + override fun isNullable(): Boolean = readMethod.returnsNullable() } /** @@ -112,7 +113,6 @@ class EvolutionPropertyReader : PropertyReader() { * making the property accessible. */ abstract class PropertyAccessor( - val initialPosition: Int, open val serializer: PropertySerializer) { companion object : Comparator { override fun compare(p0: PropertyAccessor?, p1: PropertyAccessor?): Int { @@ -120,13 +120,15 @@ abstract class PropertyAccessor( } } + open val isCalculated get() = false + /** * Override to control how the property is set on the object. */ abstract fun set(instance: Any, obj: Any?) override fun toString(): String { - return "${serializer.name}($initialPosition)" + return serializer.name } } @@ -135,9 +137,8 @@ abstract class PropertyAccessor( * is serialized and deserialized via JavaBean getter and setter style methods. */ class PropertyAccessorGetterSetter( - initialPosition: Int, getter: PropertySerializer, - private val setter: Method) : PropertyAccessor(initialPosition, getter) { + private val setter: Method) : PropertyAccessor(getter) { init { /** * Play nicely with Java interop, public methods aren't marked as accessible @@ -159,16 +160,34 @@ class PropertyAccessorGetterSetter( * of the object the property belongs to. */ class PropertyAccessorConstructor( - initialPosition: Int, - override val serializer: PropertySerializer) : PropertyAccessor(initialPosition, serializer) { + val initialPosition: Int, + override val serializer: PropertySerializer) : PropertyAccessor(serializer) { /** - * Because the property should be being set on the obejct through the constructor any + * Because the property should be being set on the object 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.") } + + override fun toString(): String = + "${serializer.name}($initialPosition)" +} + +/** + * Implementation of [PropertyAccessor] representing a calculated property of an object that is serialized + * so that it can be used by the class carpenter, but ignored on deserialisation as there is no setter or + * constructor parameter to receive its value. + * + * This will only be created for calculated properties that are accessible via no-argument methods annotated + * with [SerializableCalculatedProperty]. + */ +class CalculatedPropertyAccessor(override val serializer: PropertySerializer): PropertyAccessor(serializer) { + override val isCalculated: Boolean + get() = true + + override fun set(instance: Any, obj: Any?) = Unit // do nothing, as it's a calculated value } /** @@ -186,7 +205,7 @@ abstract class PropertySerializers( val serializationOrder: List) { companion object { fun make(serializationOrder: List) = - when (serializationOrder.firstOrNull()) { + when (serializationOrder.find { !it.isCalculated }) { is PropertyAccessorConstructor -> PropertySerializersConstructor(serializationOrder) is PropertyAccessorGetterSetter -> PropertySerializersSetter(serializationOrder) null -> PropertySerializersNoProperties() @@ -198,6 +217,7 @@ abstract class PropertySerializers( val size get() = serializationOrder.size abstract val byConstructor: Boolean + val deserializableSize = serializationOrder.count { !it.isCalculated } } class PropertySerializersNoProperties : PropertySerializers(emptyList()) { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt index f2820cc505..aed85b9825 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt @@ -3,10 +3,7 @@ package net.corda.serialization.internal.amqp import com.google.common.primitives.Primitives import com.google.common.reflect.TypeToken import net.corda.core.internal.isConcreteClass -import net.corda.core.serialization.ClassWhitelist -import net.corda.core.serialization.ConstructorForDeserialization -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.* import org.apache.qpid.proton.codec.Data import java.lang.reflect.* import java.lang.reflect.Field @@ -68,12 +65,33 @@ fun propertiesForSerialization( kotlinConstructor: KFunction?, type: Type, factory: SerializerFactory): PropertySerializers = PropertySerializers.make( - if (kotlinConstructor != null) { - propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) - } else { - propertiesForSerializationFromAbstract(type.asClass(), type, factory) - }.sortedWith(PropertyAccessor) - ) + getValueProperties(kotlinConstructor, type, factory) + .addCalculatedProperties(factory, type) + .sortedWith(PropertyAccessor)) + +fun getValueProperties(kotlinConstructor: KFunction?, type: Type, factory: SerializerFactory) + : List = + if (kotlinConstructor != null) { + propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) + } else { + propertiesForSerializationFromAbstract(type.asClass(), type, factory) + } + +private fun List.addCalculatedProperties(factory: SerializerFactory, type: Type) + : List { + val nonCalculated = map { it.serializer.name }.toSet() + return this + type.asClass().calculatedPropertyDescriptors().mapNotNull { (name, descriptor) -> + if (name in nonCalculated) null else { + val calculatedPropertyMethod = descriptor.getter + ?: throw IllegalStateException("Property $name is not a calculated property") + CalculatedPropertyAccessor(PropertySerializer.make( + name, + PublicPropertyReader(calculatedPropertyMethod), + calculatedPropertyMethod.genericReturnType, + factory)) + } + } +} /** * From a constructor, determine which properties of a class are to be serialized. @@ -145,7 +163,7 @@ fun propertiesForSerializationFromSetters( properties: Map, type: Type, factory: SerializerFactory): List = - properties.asSequence().withIndex().map { (index, entry) -> + properties.asSequence().map { entry -> val (name, property) = entry val getter = property.getter @@ -154,7 +172,6 @@ fun propertiesForSerializationFromSetters( if (getter == null || setter == null) return@map null PropertyAccessorGetterSetter( - index, PropertySerializer.make( name, PublicPropertyReader(getter), @@ -191,9 +208,9 @@ private fun propertiesForSerializationFromAbstract( clazz: Class<*>, type: Type, factory: SerializerFactory): List = - clazz.propertyDescriptors().asSequence().withIndex().map { (index, entry) -> + clazz.propertyDescriptors().asSequence().withIndex().mapNotNull { (index, entry) -> val (name, property) = entry - if (property.getter == null || property.field == null) return@map null + if (property.getter == null || property.field == null) return@mapNotNull null val getter = property.getter val returnType = resolveTypeVariables(getter.genericReturnType, type) @@ -201,7 +218,7 @@ private fun propertiesForSerializationFromAbstract( PropertyAccessorConstructor( index, PropertySerializer.make(name, PublicPropertyReader(getter), returnType, factory)) - }.filterNotNull().toList() + }.toList() internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List = exploreType(type, serializerFactory).toList() diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 8c869e2eda..cfce4fa76e 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -7,10 +7,7 @@ import net.corda.core.StubOutForDJVM import net.corda.core.internal.kotlinObjectInstance import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.ClassWhitelist -import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.serialization.internal.carpenter.* import org.apache.qpid.proton.amqp.* import java.io.NotSerializableException @@ -30,7 +27,7 @@ data class CustomSerializersCacheKey(val clazz: Class<*>, val declaredType: Type /** * Factory of serializers designed to be shared across threads and invocations. * - * @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal + * @property evolutionSerializerProvider 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. * @property onlyCustomSerializers used for testing, when set will cause the factory to throw a @@ -52,7 +49,7 @@ data class CustomSerializersCacheKey(val clazz: Class<*>, val declaredType: Type open class SerializerFactory( val whitelist: ClassWhitelist, val classCarpenter: ClassCarpenter, - private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), + private val evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider, val fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter, private val serializersByType: MutableMap>, val serializersByDescriptor: MutableMap>, @@ -64,13 +61,13 @@ open class SerializerFactory( @DeleteForDJVM constructor(whitelist: ClassWhitelist, classCarpenter: ClassCarpenter, - evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), + evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider, fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter, onlyCustomSerializers: Boolean = false ) : this( whitelist, classCarpenter, - evolutionSerializerGetter, + evolutionSerializerProvider, fingerPrinterConstructor, ConcurrentHashMap(), ConcurrentHashMap(), @@ -84,13 +81,13 @@ open class SerializerFactory( constructor(whitelist: ClassWhitelist, carpenterClassLoader: ClassLoader, lenientCarpenter: Boolean = false, - evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), + evolutionSerializerProvider: EvolutionSerializerProvider = DefaultEvolutionSerializerProvider, fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter, onlyCustomSerializers: Boolean = false ) : this( whitelist, ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), - evolutionSerializerGetter, + evolutionSerializerProvider, fingerPrinterConstructor, onlyCustomSerializers) @@ -98,12 +95,6 @@ open class SerializerFactory( val classloader: ClassLoader get() = classCarpenter.classloader - private fun getEvolutionSerializer(typeNotation: TypeNotation, - newSerializer: AMQPSerializer, - schemas: SerializationSchemas): AMQPSerializer { - return evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) - } - /** * Look up, and manufacture if necessary, a serializer for the given type. * @@ -117,11 +108,11 @@ open class SerializerFactory( val declaredClass = declaredType.asClass() val actualType: Type = if (actualClass == null) declaredType - else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType + else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType val serializer = when { - // Declared class may not be set to Collection, but actual class could be a collection. - // In this case use of CollectionSerializer is perfectly appropriate. + // Declared class may not be set to Collection, but actual class could be a collection. + // In this case use of CollectionSerializer is perfectly appropriate. (Collection::class.java.isAssignableFrom(declaredClass) || (actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) && !EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> { @@ -130,8 +121,8 @@ open class SerializerFactory( CollectionSerializer(declaredTypeAmended, this) } } - // Declared class may not be set to Map, but actual class could be a map. - // In this case use of MapSerializer is perfectly appropriate. + // Declared class may not be set to Map, but actual class could be a map. + // In this case use of MapSerializer is perfectly appropriate. (Map::class.java.isAssignableFrom(declaredClass) || (actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> { val declaredTypeAmended = MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass) @@ -142,8 +133,8 @@ open class SerializerFactory( Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> { logger.trace { "class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " + - "declaredType=${declaredType.typeName} " + - "isEnum=${declaredType::class.java.isEnum}" + "declaredType=${declaredType.typeName} " + + "isEnum=${declaredType::class.java.isEnum}" } serializersByType.computeIfAbsent(actualClass ?: declaredClass) { @@ -203,33 +194,86 @@ open class SerializerFactory( * if not, use the [ClassCarpenter] to generate a class to use in its place. */ private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) { - val metaSchema = CarpenterMetaSchema.newInstance() - for (typeNotation in schemaAndDescriptor.schemas.schema.types) { - logger.trace { "descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}" } + val requiringCarpentry = schemaAndDescriptor.schemas.schema.types.mapNotNull { typeNotation -> try { - val serialiser = processSchemaEntry(typeNotation) - // 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 EvolutionSerializer - if (serialiser.typeDescriptor != typeNotation.descriptor.name) { - logger.trace { "typeNotation=${typeNotation.name} action=\"requires Evolution\"" } - getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas) - } + getOrRegisterSerializer(schemaAndDescriptor, typeNotation) + return@mapNotNull null } catch (e: ClassNotFoundException) { if (sentinel) { logger.error("typeNotation=${typeNotation.name} error=\"after Carpentry attempt failed to load\"") throw e } - else { - logger.trace { "typeNotation=\"${typeNotation.name}\" action=\"carpentry required\"" } - } - metaSchema.buildFor(typeNotation, classloader) + logger.trace { "typeNotation=\"${typeNotation.name}\" action=\"carpentry required\"" } + return@mapNotNull typeNotation + } + }.toList() + + if (requiringCarpentry.isEmpty()) return + + runCarpentry(schemaAndDescriptor, CarpenterMetaSchema.buildWith(classloader, requiringCarpentry)) + } + + private fun getOrRegisterSerializer(schemaAndDescriptor: FactorySchemaAndDescriptor, typeNotation: TypeNotation) { + logger.trace { "descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}" } + val serialiser = processSchemaEntry(typeNotation) + + // if we just successfully built a serializer for the type but the type fingerprint + // doesn't match that of the serialised object then we may be dealing with different + // instance of the class, and such we need to build an EvolutionSerializer + if (serialiser.typeDescriptor == typeNotation.descriptor.name) return + + logger.trace { "typeNotation=${typeNotation.name} action=\"requires Evolution\"" } + evolutionSerializerProvider.getEvolutionSerializer(this, typeNotation, serialiser, schemaAndDescriptor.schemas) + } + + private fun processSchemaEntry(typeNotation: TypeNotation) = when (typeNotation) { + // java.lang.Class (whether a class or interface) + is CompositeType -> { + logger.trace("typeNotation=${typeNotation.name} amqpType=CompositeType") + processCompositeType(typeNotation) + } + // Collection / Map, possibly with generics + is RestrictedType -> { + logger.trace("typeNotation=${typeNotation.name} amqpType=RestrictedType") + processRestrictedType(typeNotation) + } + } + + // TODO: class loader logic, and compare the schema. + private fun processRestrictedType(typeNotation: RestrictedType) = + get(null, typeForName(typeNotation.name, classloader)) + + private fun processCompositeType(typeNotation: CompositeType): AMQPSerializer { + // TODO: class loader logic, and compare the schema. + val type = typeForName(typeNotation.name, classloader) + return get(type.asClass(), type) + } + + private fun typeForName(name: String, classloader: ClassLoader): Type = when { + name.endsWith("[]") -> { + val elementType = typeForName(name.substring(0, name.lastIndex - 1), classloader) + if (elementType is ParameterizedType || elementType is GenericArrayType) { + DeserializedGenericArrayType(elementType) + } else if (elementType is Class<*>) { + java.lang.reflect.Array.newInstance(elementType, 0).javaClass + } else { + throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name") } } - - if (metaSchema.isNotEmpty()) { - runCarpentry(schemaAndDescriptor, metaSchema) - } + name.endsWith("[p]") -> // There is no need to handle the ByteArray case as that type is coercible automatically + // to the binary type and is thus handled by the main serializer and doesn't need a + // special case for a primitive array of bytes + when (name) { + "int[p]" -> IntArray::class.java + "char[p]" -> CharArray::class.java + "boolean[p]" -> BooleanArray::class.java + "float[p]" -> FloatArray::class.java + "double[p]" -> DoubleArray::class.java + "short[p]" -> ShortArray::class.java + "long[p]" -> LongArray::class.java + else -> throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name") + } + else -> DeserializedParameterizedType.make(name, classloader) } @StubOutForDJVM @@ -251,31 +295,6 @@ open class SerializerFactory( processSchema(schemaAndDescriptor, true) } - private fun processSchemaEntry(typeNotation: TypeNotation) = when (typeNotation) { - // java.lang.Class (whether a class or interface) - is CompositeType -> { - logger.trace("typeNotation=${typeNotation.name} amqpType=CompositeType") - processCompositeType(typeNotation) - } - // Collection / Map, possibly with generics - is RestrictedType -> { - logger.trace("typeNotation=${typeNotation.name} amqpType=RestrictedType") - processRestrictedType(typeNotation) - } - } - - // TODO: class loader logic, and compare the schema. - private fun processRestrictedType(typeNotation: RestrictedType) = get(null, - typeForName(typeNotation.name, classloader)) - - private fun processCompositeType(typeNotation: CompositeType): AMQPSerializer { - // TODO: class loader logic, and compare the schema. - val type = typeForName(typeNotation.name, classloader) - return get( - type.asClass(), - type) - } - private fun makeClassSerializer( clazz: Class<*>, type: Type, @@ -352,6 +371,9 @@ open class SerializerFactory( return MapSerializer(declaredType, this) } + fun registerByDescriptor(name: Symbol, serializerCreator: () -> AMQPSerializer): AMQPSerializer = + serializersByDescriptor.computeIfAbsent(name) { _ -> serializerCreator() } + companion object { private val logger = contextLogger() @@ -405,35 +427,6 @@ open class SerializerFactory( is TypeVariable<*> -> "?" else -> throw AMQPNotSerializableException(type, "Unable to render type $type to a string.") } - - private fun typeForName(name: String, classloader: ClassLoader): Type { - return if (name.endsWith("[]")) { - val elementType = typeForName(name.substring(0, name.lastIndex - 1), classloader) - if (elementType is ParameterizedType || elementType is GenericArrayType) { - DeserializedGenericArrayType(elementType) - } else if (elementType is Class<*>) { - java.lang.reflect.Array.newInstance(elementType, 0).javaClass - } else { - throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name") - } - } else if (name.endsWith("[p]")) { - // There is no need to handle the ByteArray case as that type is coercible automatically - // to the binary type and is thus handled by the main serializer and doesn't need a - // special case for a primitive array of bytes - when (name) { - "int[p]" -> IntArray::class.java - "char[p]" -> CharArray::class.java - "boolean[p]" -> BooleanArray::class.java - "float[p]" -> FloatArray::class.java - "double[p]" -> DoubleArray::class.java - "short[p]" -> ShortArray::class.java - "long[p]" -> LongArray::class.java - else -> throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name") - } - } else { - DeserializedParameterizedType.make(name, classloader) - } - } } object AnyType : WildcardType { @@ -443,4 +436,4 @@ open class SerializerFactory( override fun toString(): String = "?" } -} +} \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt index c85bb6ebd6..b5e6593286 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt @@ -32,6 +32,11 @@ data class CarpenterMetaSchema( val dependencies: MutableMap>>, val dependsOn: MutableMap>) { companion object CarpenterSchemaConstructor { + fun buildWith(classLoader: ClassLoader, types: List) = + newInstance().apply { + types.forEach { buildFor(it, classLoader) } + } + fun newInstance(): CarpenterMetaSchema { return CarpenterMetaSchema(mutableListOf(), mutableMapOf(), mutableMapOf()) } diff --git a/serialization/src/test/java/net/corda/serialization/internal/LambdaCheckpointSerializationTest.java b/serialization/src/test/java/net/corda/serialization/internal/LambdaCheckpointSerializationTest.java index feab89ad92..0c9c9f2b5a 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/LambdaCheckpointSerializationTest.java +++ b/serialization/src/test/java/net/corda/serialization/internal/LambdaCheckpointSerializationTest.java @@ -2,14 +2,14 @@ package net.corda.serialization.internal; import net.corda.core.serialization.*; import net.corda.core.serialization.internal.CheckpointSerializationContext; -import net.corda.core.serialization.internal.CheckpointSerializationFactory; +import net.corda.core.serialization.internal.CheckpointSerializer; import net.corda.node.serialization.kryo.CordaClosureSerializer; -import net.corda.testing.core.SerializationEnvironmentRule; import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import java.io.NotSerializableException; import java.io.Serializable; import java.util.Collections; import java.util.concurrent.Callable; @@ -23,12 +23,11 @@ public final class LambdaCheckpointSerializationTest { public final CheckpointSerializationEnvironmentRule testCheckpointSerialization = new CheckpointSerializationEnvironmentRule(); - private CheckpointSerializationFactory factory; private CheckpointSerializationContext context; + private CheckpointSerializer serializer; @Before public void setup() { - factory = testCheckpointSerialization.getCheckpointSerializationFactory(); context = new CheckpointSerializationContextImpl( getClass().getClassLoader(), AllWhitelist.INSTANCE, @@ -36,6 +35,8 @@ public final class LambdaCheckpointSerializationTest { true, null ); + + serializer = testCheckpointSerialization.getCheckpointSerializer(); } @Test @@ -63,11 +64,11 @@ public final class LambdaCheckpointSerializationTest { assertThat(throwable).hasMessage(CordaClosureSerializer.ERROR_MESSAGE); } - private SerializedBytes serialize(final T target) { - return factory.serialize(target, context); + private SerializedBytes serialize(final T target) throws NotSerializableException { + return serializer.serialize(target, context); } - private T deserialize(final SerializedBytes bytes, final Class type) { - return factory.deserialize(bytes, type, context); + private T deserialize(final SerializedBytes bytes, final Class type) throws NotSerializableException { + return serializer.deserialize(bytes, type, context); } } diff --git a/serialization/src/test/java/net/corda/serialization/internal/carpenter/JavaCalculatedValuesToClassCarpenterTest.java b/serialization/src/test/java/net/corda/serialization/internal/carpenter/JavaCalculatedValuesToClassCarpenterTest.java new file mode 100644 index 0000000000..1171d7713d --- /dev/null +++ b/serialization/src/test/java/net/corda/serialization/internal/carpenter/JavaCalculatedValuesToClassCarpenterTest.java @@ -0,0 +1,107 @@ +package net.corda.serialization.internal.carpenter; + +import net.corda.core.serialization.SerializableCalculatedProperty; +import net.corda.core.serialization.SerializationContext; +import net.corda.core.serialization.SerializationFactory; +import net.corda.core.serialization.SerializedBytes; +import net.corda.serialization.internal.AllWhitelist; +import net.corda.serialization.internal.amqp.*; +import net.corda.serialization.internal.amqp.Schema; +import net.corda.testing.core.SerializationEnvironmentRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import static java.util.Collections.singletonList; +import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactoryNoEvolution; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JavaCalculatedValuesToClassCarpenterTest extends AmqpCarpenterBase { + public JavaCalculatedValuesToClassCarpenterTest() { + super(AllWhitelist.INSTANCE); + } + + public interface Parent { + @SerializableCalculatedProperty + int getDoubled(); + } + + public static final class C implements Parent { + private final int i; + + public C(int i) { + this.i = i; + } + + @SerializableCalculatedProperty + public String getSquared() { + return Integer.toString(i * i); + } + + @Override + public int getDoubled() { + return i * 2; + } + + public int getI() { + return i; + } + } + + @Rule + public final SerializationEnvironmentRule serializationEnvironmentRule = new SerializationEnvironmentRule(); + private SerializationContext context; + + @Before + public void initSerialization() { + SerializationFactory factory = serializationEnvironmentRule.getSerializationFactory(); + context = factory.getDefaultContext(); + } + + @Test + public void calculatedValues() throws Exception { + SerializerFactory factory = testDefaultFactoryNoEvolution(); + SerializedBytes serialized = serialise(new C(2)); + ObjectAndEnvelope objAndEnv = new DeserializationInput(factory) + .deserializeAndReturnEnvelope(serialized, C.class, context); + + C amqpObj = objAndEnv.getObj(); + Schema schema = objAndEnv.getEnvelope().getSchema(); + + assertEquals(2, amqpObj.getI()); + assertEquals("4", amqpObj.getSquared()); + assertEquals(2, schema.getTypes().size()); + assertTrue(schema.getTypes().get(0) instanceof CompositeType); + + CompositeType concrete = (CompositeType) schema.getTypes().get(0); + assertEquals(3, concrete.getFields().size()); + assertEquals("doubled", concrete.getFields().get(0).getName()); + assertEquals("int", concrete.getFields().get(0).getType()); + assertEquals("i", concrete.getFields().get(1).getName()); + assertEquals("int", concrete.getFields().get(1).getType()); + assertEquals("squared", concrete.getFields().get(2).getName()); + assertEquals("string", concrete.getFields().get(2).getType()); + + assertEquals(0, AMQPSchemaExtensions.carpenterSchema(schema, ClassLoader.getSystemClassLoader()).getSize()); + Schema mangledSchema = ClassCarpenterTestUtilsKt.mangleNames(schema, singletonList(C.class.getTypeName())); + CarpenterMetaSchema l2 = AMQPSchemaExtensions.carpenterSchema(mangledSchema, ClassLoader.getSystemClassLoader()); + String mangledClassName = ClassCarpenterTestUtilsKt.mangleName(C.class.getTypeName()); + + assertEquals(1, l2.getSize()); + net.corda.serialization.internal.carpenter.Schema carpenterSchema = l2.getCarpenterSchemas().stream() + .filter(s -> s.getName().equals(mangledClassName)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No schema found for mangled class name " + mangledClassName)); + + Class pinochio = new ClassCarpenterImpl(AllWhitelist.INSTANCE).build(carpenterSchema); + Object p = pinochio.getConstructors()[0].newInstance(4, 2, "4"); + + assertEquals(pinochio.getMethod("getI").invoke(p), amqpObj.getI()); + assertEquals(pinochio.getMethod("getSquared").invoke(p), amqpObj.getSquared()); + assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.getDoubled()); + + Parent upcast = (Parent) p; + assertEquals(upcast.getDoubled(), amqpObj.getDoubled()); + } +} diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/ContractAttachmentSerializerTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/ContractAttachmentSerializerTest.kt index 73b799217d..e3426a1fd9 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/ContractAttachmentSerializerTest.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/ContractAttachmentSerializerTest.kt @@ -4,11 +4,9 @@ import net.corda.core.contracts.ContractAttachment import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.* import net.corda.core.serialization.internal.CheckpointSerializationContext -import net.corda.core.serialization.internal.CheckpointSerializationFactory import net.corda.core.serialization.internal.checkpointDeserialize import net.corda.core.serialization.internal.checkpointSerialize import net.corda.testing.contracts.DummyContract -import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices @@ -27,24 +25,25 @@ class ContractAttachmentSerializerTest { @JvmField val testCheckpointSerialization = CheckpointSerializationEnvironmentRule() - private lateinit var factory: CheckpointSerializationFactory - private lateinit var context: CheckpointSerializationContext private lateinit var contextWithToken: CheckpointSerializationContext private val mockServices = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"), rigorousMock()) @Before fun setup() { - factory = testCheckpointSerialization.checkpointSerializationFactory - context = testCheckpointSerialization.checkpointSerializationContext - contextWithToken = context.withTokenContext(CheckpointSerializeAsTokenContextImpl(Any(), factory, context, mockServices)) + contextWithToken = testCheckpointSerialization.checkpointSerializationContext.withTokenContext( + CheckpointSerializeAsTokenContextImpl( + Any(), + testCheckpointSerialization.checkpointSerializer, + testCheckpointSerialization.checkpointSerializationContext, + mockServices)) } @Test fun `write contract attachment and read it back`() { val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY), DummyContract.PROGRAM_ID) // no token context so will serialize the whole attachment - val serialized = contractAttachment.checkpointSerialize(factory, context) - val deserialized = serialized.checkpointDeserialize(factory, context) + val serialized = contractAttachment.checkpointSerialize() + val deserialized = serialized.checkpointDeserialize() assertEquals(contractAttachment.id, deserialized.attachment.id) assertEquals(contractAttachment.contract, deserialized.contract) @@ -59,8 +58,8 @@ class ContractAttachmentSerializerTest { mockServices.attachments.importAttachment(attachment.open(), "test", null) val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID) - val serialized = contractAttachment.checkpointSerialize(factory, contextWithToken) - val deserialized = serialized.checkpointDeserialize(factory, contextWithToken) + val serialized = contractAttachment.checkpointSerialize(contextWithToken) + val deserialized = serialized.checkpointDeserialize(contextWithToken) assertEquals(contractAttachment.id, deserialized.attachment.id) assertEquals(contractAttachment.contract, deserialized.contract) @@ -76,7 +75,7 @@ class ContractAttachmentSerializerTest { mockServices.attachments.importAttachment(attachment.open(), "test", null) val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID) - val serialized = contractAttachment.checkpointSerialize(factory, contextWithToken) + val serialized = contractAttachment.checkpointSerialize(contextWithToken) assertThat(serialized.size).isLessThan(largeAttachmentSize) } @@ -88,8 +87,8 @@ class ContractAttachmentSerializerTest { // don't importAttachment in mockService val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID) - val serialized = contractAttachment.checkpointSerialize(factory, contextWithToken) - val deserialized = serialized.checkpointDeserialize(factory, contextWithToken) + val serialized = contractAttachment.checkpointSerialize(contextWithToken) + val deserialized = serialized.checkpointDeserialize(contextWithToken) assertThatThrownBy { deserialized.attachment.open() }.isInstanceOf(MissingAttachmentsException::class.java) } @@ -100,8 +99,8 @@ class ContractAttachmentSerializerTest { // don't importAttachment in mockService val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID) - val serialized = contractAttachment.checkpointSerialize(factory, contextWithToken) - serialized.checkpointDeserialize(factory, contextWithToken) + val serialized = contractAttachment.checkpointSerialize(contextWithToken) + serialized.checkpointDeserialize(contextWithToken) // MissingAttachmentsException thrown if we try to open attachment } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/SerializationTokenTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/SerializationTokenTest.kt index 7f2bad6854..49e5d1f2ed 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/SerializationTokenTest.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/SerializationTokenTest.kt @@ -5,7 +5,6 @@ import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.io.Output import net.corda.core.serialization.* import net.corda.core.serialization.internal.CheckpointSerializationContext -import net.corda.core.serialization.internal.CheckpointSerializationFactory import net.corda.core.serialization.internal.checkpointDeserialize import net.corda.core.serialization.internal.checkpointSerialize import net.corda.core.utilities.OpaqueBytes @@ -14,7 +13,6 @@ import net.corda.node.serialization.kryo.CordaKryo import net.corda.node.serialization.kryo.DefaultKryoCustomizer import net.corda.node.serialization.kryo.kryoMagic import net.corda.testing.internal.rigorousMock -import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -28,12 +26,10 @@ class SerializationTokenTest { @JvmField val testCheckpointSerialization = CheckpointSerializationEnvironmentRule() - private lateinit var factory: CheckpointSerializationFactory private lateinit var context: CheckpointSerializationContext @Before fun setup() { - factory = testCheckpointSerialization.checkpointSerializationFactory context = testCheckpointSerialization.checkpointSerializationContext.withWhitelisted(SingletonSerializationToken::class.java) } @@ -49,16 +45,16 @@ class SerializationTokenTest { override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size } - private fun serializeAsTokenContext(toBeTokenized: Any) = CheckpointSerializeAsTokenContextImpl(toBeTokenized, factory, context, rigorousMock()) + private fun serializeAsTokenContext(toBeTokenized: Any) = CheckpointSerializeAsTokenContextImpl(toBeTokenized, testCheckpointSerialization.checkpointSerializer, context, rigorousMock()) @Test fun `write token and read tokenizable`() { val tokenizableBefore = LargeTokenizable() val context = serializeAsTokenContext(tokenizableBefore) val testContext = this.context.withTokenContext(context) - val serializedBytes = tokenizableBefore.checkpointSerialize(factory, testContext) + val serializedBytes = tokenizableBefore.checkpointSerialize(testContext) assertThat(serializedBytes.size).isLessThan(tokenizableBefore.numBytes) - val tokenizableAfter = serializedBytes.checkpointDeserialize(factory, testContext) + val tokenizableAfter = serializedBytes.checkpointDeserialize(testContext) assertThat(tokenizableAfter).isSameAs(tokenizableBefore) } @@ -69,8 +65,8 @@ class SerializationTokenTest { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(tokenizableBefore) val testContext = this.context.withTokenContext(context) - val serializedBytes = tokenizableBefore.checkpointSerialize(factory, testContext) - val tokenizableAfter = serializedBytes.checkpointDeserialize(factory, testContext) + val serializedBytes = tokenizableBefore.checkpointSerialize(testContext) + val tokenizableAfter = serializedBytes.checkpointDeserialize(testContext) assertThat(tokenizableAfter).isSameAs(tokenizableBefore) } @@ -79,7 +75,7 @@ class SerializationTokenTest { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(emptyList()) val testContext = this.context.withTokenContext(context) - tokenizableBefore.checkpointSerialize(factory, testContext) + tokenizableBefore.checkpointSerialize(testContext) } @Test(expected = UnsupportedOperationException::class) @@ -87,14 +83,14 @@ class SerializationTokenTest { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(emptyList()) val testContext = this.context.withTokenContext(context) - val serializedBytes = tokenizableBefore.toToken(serializeAsTokenContext(emptyList())).checkpointSerialize(factory, testContext) - serializedBytes.checkpointDeserialize(factory, testContext) + val serializedBytes = tokenizableBefore.toToken(serializeAsTokenContext(emptyList())).checkpointSerialize(testContext) + serializedBytes.checkpointDeserialize(testContext) } @Test(expected = KryoException::class) fun `no context set`() { val tokenizableBefore = UnitSerializeAsToken() - tokenizableBefore.checkpointSerialize(factory, context) + tokenizableBefore.checkpointSerialize(context) } @Test(expected = KryoException::class) @@ -112,7 +108,7 @@ class SerializationTokenTest { kryo.writeObject(it, emptyList()) } val serializedBytes = SerializedBytes(stream.toByteArray()) - serializedBytes.checkpointDeserialize(factory, testContext) + serializedBytes.checkpointDeserialize(testContext) } private class WrongTypeSerializeAsToken : SerializeAsToken { @@ -128,7 +124,7 @@ class SerializationTokenTest { val tokenizableBefore = WrongTypeSerializeAsToken() val context = serializeAsTokenContext(tokenizableBefore) val testContext = this.context.withTokenContext(context) - val serializedBytes = tokenizableBefore.checkpointSerialize(factory, testContext) - serializedBytes.checkpointDeserialize(factory, testContext) + val serializedBytes = tokenizableBefore.checkpointSerialize(testContext) + serializedBytes.checkpointDeserialize(testContext) } } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerGetterTesting.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerProviderTesting.kt similarity index 85% rename from serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerGetterTesting.kt rename to serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerProviderTesting.kt index 74ab702687..7239e6a467 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerGetterTesting.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerProviderTesting.kt @@ -3,12 +3,12 @@ package net.corda.serialization.internal.amqp import java.io.NotSerializableException /** - * An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a + * An implementation of [EvolutionSerializerProvider] 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() { +object FailIfEvolutionAttempted : EvolutionSerializerProvider { override fun getEvolutionSerializer(factory: SerializerFactory, typeNotation: TypeNotation, newSerializer: AMQPSerializer, diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt index 023f291394..6d88f3ccf2 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt @@ -42,7 +42,7 @@ class FingerPrinterTestingTests { val factory = SerializerFactory( AllWhitelist, ClassLoader.getSystemClassLoader(), - evolutionSerializerGetter = EvolutionSerializerGetterTesting(), + evolutionSerializerProvider = FailIfEvolutionAttempted, fingerPrinterConstructor = { _ -> FingerPrinterTesting() }) val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L)) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/RoundTripTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/RoundTripTests.kt index 381032cfbd..5f9ee087b3 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/RoundTripTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/RoundTripTests.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.amqp import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.core.serialization.SerializableCalculatedProperty import net.corda.serialization.internal.amqp.testutils.deserialize import net.corda.serialization.internal.amqp.testutils.serialize import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution @@ -61,4 +62,62 @@ class RoundTripTests { val newC = DeserializationInput(factory).deserialize(bytes) newC.copy(l = (newC.l + "d")) } + + @Test + fun calculatedValues() { + data class C(val i: Int) { + @get:SerializableCalculatedProperty + val squared = i * i + } + + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(C(2)) + val deserialized = DeserializationInput(factory).deserialize(bytes) + assertThat(deserialized.squared).isEqualTo(4) + } + + @Test + fun calculatedFunction() { + class C { + var i: Int = 0 + @SerializableCalculatedProperty + fun getSquared() = i * i + } + + val instance = C().apply { i = 2 } + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(instance) + val deserialized = DeserializationInput(factory).deserialize(bytes) + assertThat(deserialized.getSquared()).isEqualTo(4) + } + + interface I { + @get:SerializableCalculatedProperty + val squared: Int + } + + @Test + fun inheritedCalculatedFunction() { + class C: I { + var i: Int = 0 + override val squared get() = i * i + } + + val instance = C().apply { i = 2 } + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(instance) + val deserialized = DeserializationInput(factory).deserialize(bytes) as I + assertThat(deserialized.squared).isEqualTo(4) + } + + @Test + fun inheritedCalculatedFunctionIsNotCalculated() { + class C(override val squared: Int): I + + val instance = C(2) + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(instance) + val deserialized = DeserializationInput(factory).deserialize(bytes) as I + assertThat(deserialized.squared).isEqualTo(2) + } } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt index 37bf6e8b47..a0259ca36c 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt @@ -210,7 +210,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi return SerializerFactory( AllWhitelist, ClassLoader.getSystemClassLoader(), - evolutionSerializerGetter = EvolutionSerializerGetterTesting() + evolutionSerializerProvider = FailIfEvolutionAttempted ) } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt index e3820e2d6b..ea81448da7 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt @@ -23,7 +23,7 @@ fun testDefaultFactoryNoEvolution(): SerializerFactory { return SerializerFactory( AllWhitelist, ClassLoader.getSystemClassLoader(), - evolutionSerializerGetter = EvolutionSerializerGetterTesting()) + evolutionSerializerProvider = FailIfEvolutionAttempted) } fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CalculatedValuesToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CalculatedValuesToClassCarpenterTests.kt new file mode 100644 index 0000000000..893f81833b --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CalculatedValuesToClassCarpenterTests.kt @@ -0,0 +1,101 @@ +package net.corda.serialization.internal.carpenter + +import net.corda.core.serialization.SerializableCalculatedProperty +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.CompositeType +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope +import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution +import org.junit.Test +import kotlin.test.assertEquals + +class CalculatedValuesToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { + + interface Parent { + @get:SerializableCalculatedProperty + val doubled: Int + } + + @Test + fun calculatedValues() { + data class C(val i: Int): Parent { + @get:SerializableCalculatedProperty + val squared = (i * i).toString() + + override val doubled get() = i * 2 + } + + val factory = testDefaultFactoryNoEvolution() + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(C(2))) + val amqpObj = obj.obj + val serSchema = obj.envelope.schema + + assertEquals(2, amqpObj.i) + assertEquals("4", amqpObj.squared) + assertEquals(2, serSchema.types.size) + require(serSchema.types[0] is CompositeType) + + val concrete = serSchema.types[0] as CompositeType + assertEquals(3, concrete.fields.size) + assertEquals("doubled", concrete.fields[0].name) + assertEquals("int", concrete.fields[0].type) + assertEquals("i", concrete.fields[1].name) + assertEquals("int", concrete.fields[1].type) + assertEquals("squared", concrete.fields[2].name) + assertEquals("string", concrete.fields[2].type) + + val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) + assertEquals(0, l1.size) + val mangleSchema = serSchema.mangleNames(listOf((classTestName("C")))) + val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) + val aName = mangleName(classTestName("C")) + + assertEquals(1, l2.size) + val aSchema = l2.carpenterSchemas.find { it.name == aName }!! + + val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema) + val p = pinochio.constructors[0].newInstance(4, 2, "4") + + assertEquals(pinochio.getMethod("getI").invoke(p), amqpObj.i) + assertEquals(pinochio.getMethod("getSquared").invoke(p), amqpObj.squared) + assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.doubled) + + val upcast = p as Parent + assertEquals(upcast.doubled, amqpObj.doubled) + } + + @Test + fun implementingClassDoesNotCalculateValue() { + class C(override val doubled: Int): Parent + + val factory = testDefaultFactoryNoEvolution() + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(C(5))) + val amqpObj = obj.obj + val serSchema = obj.envelope.schema + + assertEquals(2, serSchema.types.size) + require(serSchema.types[0] is CompositeType) + + val concrete = serSchema.types[0] as CompositeType + assertEquals(1, concrete.fields.size) + assertEquals("doubled", concrete.fields[0].name) + assertEquals("int", concrete.fields[0].type) + + val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) + assertEquals(0, l1.size) + val mangleSchema = serSchema.mangleNames(listOf((classTestName("C")))) + val l2 = mangleSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) + val aName = mangleName(classTestName("C")) + + assertEquals(1, l2.size) + val aSchema = l2.carpenterSchemas.find { it.name == aName }!! + + val pinochio = ClassCarpenterImpl(whitelist = AllWhitelist).build(aSchema) + val p = pinochio.constructors[0].newInstance(5) + + assertEquals(pinochio.getMethod("getDoubled").invoke(p), amqpObj.doubled) + + val upcast = p as Parent + assertEquals(upcast.doubled, amqpObj.doubled) + } +} \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt index 22f93a5b16..5c3701dc49 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.carpenter import net.corda.core.serialization.ClassWhitelist +import net.corda.core.serialization.SerializedBytes import net.corda.serialization.internal.amqp.* import net.corda.serialization.internal.amqp.Field import net.corda.serialization.internal.amqp.Schema @@ -47,7 +48,7 @@ open class AmqpCarpenterBase(whitelist: ClassWhitelist) { var cc = ClassCarpenterImpl(whitelist = whitelist) var factory = SerializerFactoryExternalCarpenter(cc) - fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) + fun serialise(obj: T): SerializedBytes = SerializationOutput(factory).serialize(obj) @Suppress("NOTHING_TO_INLINE") inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index cca81755e9..5e5b262a6f 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -30,9 +30,7 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - require(obj.obj is B) - - val amqpObj = obj.obj as B + val amqpObj = obj.obj assertEquals(testB, amqpObj.b) assertEquals(testA, amqpObj.a.a) @@ -92,8 +90,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"))) - require(obj.obj is B) - amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) } @@ -111,8 +107,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - require(obj.obj is B) - val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) @@ -138,9 +132,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - - require(obj.obj is B) - val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) @@ -198,8 +189,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val c = C(B(testA, testB), testC) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) - require(obj.obj is C) - val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) @@ -224,8 +213,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val c = C(B(testA, testB), testC) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) - require(obj.obj is C) - val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) @@ -250,8 +237,6 @@ class CompositeMembers : AmqpCarpenterBase(AllWhitelist) { val c = C(B(testA, testB), testC) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) - require(obj.obj is C) - val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) TestMetaCarpenter(carpenterSchema.carpenterSchema( ClassLoader.getSystemClassLoader()), ClassCarpenterImpl(whitelist = AllWhitelist)) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt index b7e09664f6..6a1a2c6e3e 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -44,7 +44,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { assertEquals(testJ, a.j) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assertTrue(obj.obj is A) val serSchema = obj.envelope.schema assertEquals(2, serSchema.types.size) val l1 = serSchema.carpenterSchema(ClassLoader.getSystemClassLoader()) @@ -84,8 +83,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assertTrue(obj.obj is A) - val serSchema = obj.envelope.schema assertEquals(2, serSchema.types.size) @@ -128,8 +125,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val a = A(testI, testII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assertTrue(obj.obj is A) - val serSchema = obj.envelope.schema assertEquals(3, serSchema.types.size) @@ -176,8 +171,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val a = A(testI, testIII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assertTrue(obj.obj is A) - val serSchema = obj.envelope.schema assertEquals(3, serSchema.types.size) @@ -225,8 +218,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val b = B(a, testIIII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assertTrue(obj.obj is B) - val serSchema = obj.envelope.schema // Expected classes are @@ -281,8 +272,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val b = B(a, testIIII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assertTrue(obj.obj is B) - val serSchema = obj.envelope.schema // The classes we're expecting to find: @@ -305,8 +294,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhitelist) { val a = A(testI) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assertTrue(obj.obj is A) - val serSchema = obj.envelope.schema // The classes we're expecting to find: diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 8b50f9d996..3a560566bb 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -21,8 +21,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi val a = A(testA, testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(testA, amqpObj.a) assertEquals(testB, amqpObj.b) @@ -65,8 +64,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWhi val a = A(testA, testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(testA, amqpObj.a) assertEquals(testB, amqpObj.b) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index abcf831091..5642b0b824 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -17,9 +17,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh val test = 10 val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(test, amqpObj.a) assertEquals(1, obj.envelope.schema.types.size) @@ -53,9 +51,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(test, amqpObj.a) assertEquals(1, obj.envelope.schema.types.size) @@ -83,9 +79,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh val test = 10L val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(test, amqpObj.a) assertEquals(1, obj.envelope.schema.types.size) @@ -118,9 +112,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh val test = 10.toShort() val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(test, amqpObj.a) assertEquals(1, obj.envelope.schema.types.size) @@ -153,9 +145,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh val test = 10.0 val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(test, amqpObj.a) assertEquals(1, obj.envelope.schema.types.size) @@ -188,9 +178,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase(AllWh val test = 10.0F val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - require(obj.obj is A) - val amqpObj = obj.obj as A + val amqpObj = obj.obj assertEquals(test, amqpObj.a) assertEquals(1, obj.envelope.schema.types.size) diff --git a/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyServer.kt b/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyServer.kt index dc458a6bed..74c92c96ce 100644 --- a/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyServer.kt +++ b/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyServer.kt @@ -3,7 +3,7 @@ package net.corda.behave.service.proxy import net.corda.behave.service.proxy.RPCProxyServer.Companion.initialiseSerialization import net.corda.behave.service.proxy.RPCProxyServer.Companion.log import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger @@ -71,7 +71,7 @@ class RPCProxyServer(hostAndPort: NetworkHostAndPort, val webService: RPCProxyWe fun initialiseSerialization() { try { nodeSerializationEnv = - SerializationEnvironmentImpl( + SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPClientSerializationScheme(emptyList())) }, diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt index 514b23a855..526f1251b6 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt @@ -3,9 +3,7 @@ package net.corda.testing.core import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doAnswer import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.DoNotImplement import net.corda.core.internal.staticField -import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.testing.common.internal.asContextEnv @@ -40,7 +38,7 @@ class SerializationEnvironmentRule(private val inheritable: Boolean = false) : T /** Do not call, instead use [SerializationEnvironmentRule] as a [org.junit.Rule]. */ fun run(taskLabel: String, task: (SerializationEnvironment) -> T): T { - return SerializationEnvironmentRule().apply { init(taskLabel) }.runTask(task) + return SerializationEnvironmentRule().apply { init() }.runTask(task) } } @@ -48,14 +46,14 @@ class SerializationEnvironmentRule(private val inheritable: Boolean = false) : T val serializationFactory get() = env.serializationFactory override fun apply(base: Statement, description: Description): Statement { - init(description.toString()) + init() return object : Statement() { override fun evaluate() = runTask { base.evaluate() } } } - private fun init(envLabel: String) { - env = createTestSerializationEnv(envLabel) + private fun init() { + env = createTestSerializationEnv() } private fun runTask(task: (SerializationEnvironment) -> T): T { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/internal/CheckpointSerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/internal/CheckpointSerializationTestHelpers.kt index eb92d12cf6..69b634e107 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/internal/CheckpointSerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/internal/CheckpointSerializationTestHelpers.kt @@ -39,7 +39,7 @@ class CheckpointSerializationEnvironmentRule(private val inheritable: Boolean = /** Do not call, instead use [SerializationEnvironmentRule] as a [org.junit.Rule]. */ fun run(taskLabel: String, task: (SerializationEnvironment) -> T): T { - return CheckpointSerializationEnvironmentRule().apply { init(taskLabel) }.runTask(task) + return CheckpointSerializationEnvironmentRule().apply { init() }.runTask(task) } } @@ -47,14 +47,14 @@ class CheckpointSerializationEnvironmentRule(private val inheritable: Boolean = private lateinit var env: SerializationEnvironment override fun apply(base: Statement, description: Description): Statement { - init(description.toString()) + init() return object : Statement() { override fun evaluate() = runTask { base.evaluate() } } } - private fun init(envLabel: String) { - env = createTestSerializationEnv(envLabel) + private fun init() { + env = createTestSerializationEnv() } private fun runTask(task: (SerializationEnvironment) -> T): T { @@ -65,7 +65,6 @@ class CheckpointSerializationEnvironmentRule(private val inheritable: Boolean = } } - val checkpointSerializationFactory get() = env.checkpointSerializationFactory val checkpointSerializationContext get() = env.checkpointContext - + val checkpointSerializer get() = env.checkpointSerializer } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt index 53bee6f798..3257cca802 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt @@ -4,11 +4,10 @@ import com.nhaarman.mockito_kotlin.doNothing import com.nhaarman.mockito_kotlin.whenever import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.core.DoNotImplement -import net.corda.core.serialization.internal.CheckpointSerializationFactory import net.corda.core.serialization.internal.* import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT -import net.corda.node.serialization.kryo.KryoSerializationScheme +import net.corda.node.serialization.kryo.KryoCheckpointSerializer import net.corda.serialization.internal.* import net.corda.testing.core.SerializationEnvironmentRule import java.util.concurrent.ConcurrentHashMap @@ -30,22 +29,20 @@ fun withoutTestSerialization(callable: () -> T): T { // TODO: Delete this, s } } -internal fun createTestSerializationEnv(label: String): SerializationEnvironmentImpl { +internal fun createTestSerializationEnv(): SerializationEnvironment { val factory = SerializationFactoryImpl().apply { registerScheme(AMQPClientSerializationScheme(emptyList())) registerScheme(AMQPServerSerializationScheme(emptyList())) } - return object : SerializationEnvironmentImpl( + return SerializationEnvironment.with( factory, AMQP_P2P_CONTEXT, AMQP_RPC_SERVER_CONTEXT, AMQP_RPC_CLIENT_CONTEXT, AMQP_STORAGE_CONTEXT, KRYO_CHECKPOINT_CONTEXT, - CheckpointSerializationFactory(KryoSerializationScheme) - ) { - override fun toString() = "testSerializationEnv($label)" - } + KryoCheckpointSerializer + ) } /** @@ -54,7 +51,7 @@ internal fun createTestSerializationEnv(label: String): SerializationEnvironment */ fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment { return if (armed) { - object : GlobalSerializationEnvironment, SerializationEnvironment by createTestSerializationEnv("") { + object : GlobalSerializationEnvironment, SerializationEnvironment by createTestSerializationEnv() { override fun unset() { _globalSerializationEnv.set(null) inVMExecutors.remove(this) diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt index 570a9cbe2e..8887247bf6 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt @@ -8,11 +8,10 @@ import net.corda.cliutils.CordaCliWrapper import net.corda.cliutils.ExitCodes import net.corda.cliutils.start import net.corda.core.internal.isRegularFile -import net.corda.core.internal.rootMessage import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.utilities.base64ToByteArray import net.corda.core.utilities.hexToByteArray @@ -22,7 +21,6 @@ import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.DeserializationInput import net.corda.serialization.internal.amqp.amqpMagic import org.slf4j.event.Level -import picocli.CommandLine import picocli.CommandLine.* import java.io.PrintStream import java.net.MalformedURLException @@ -36,8 +34,8 @@ fun main(args: Array) { } class BlobInspector : CordaCliWrapper("blob-inspector", "Convert AMQP serialised binary blobs to text") { - @Parameters(index = "*..0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class]) - var source: MutableList = mutableListOf() + @Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class]) + var source: URL? = null @Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"]) private var formatType: OutputFormatType = OutputFormatType.YAML @@ -63,8 +61,7 @@ class BlobInspector : CordaCliWrapper("blob-inspector", "Convert AMQP serialised } fun run(out: PrintStream): Int { - require(source.count() == 1) { "You must specify URL or file path to the blob" } - val inputBytes = source.first().readBytes() + val inputBytes = source!!.readBytes() val bytes = parseToBinaryRelaxed(inputFormatType, inputBytes) ?: throw IllegalArgumentException("Error: this input does not appear to be encoded in Corda's AMQP extended format, sorry.") @@ -128,7 +125,7 @@ class BlobInspector : CordaCliWrapper("blob-inspector", "Convert AMQP serialised private fun initialiseSerialization() { // Deserialise with the lenient carpenter as we only care for the AMQP field getters - _contextSerializationEnv.set(SerializationEnvironmentImpl( + _contextSerializationEnv.set(SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPInspectorSerializationScheme) }, diff --git a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt index 04cc72d621..65e1223c4f 100644 --- a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt +++ b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt @@ -53,7 +53,7 @@ class BlobInspectorTest { } private fun run(resourceName: String): String { - blobInspector.source = mutableListOf(javaClass.getResource(resourceName)) + blobInspector.source = javaClass.getResource(resourceName) val writer = StringWriter() blobInspector.run(PrintStream(WriterOutputStream(writer, UTF_8))) val output = writer.toString() diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt index f30c3ed05d..5a35be8089 100644 --- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt @@ -21,8 +21,6 @@ import java.util.concurrent.Callable interface Validated { companion object { val logger = contextLogger() - const val RED = "\u001B[31m" - const val RESET = "\u001B[0m" } /** @@ -36,8 +34,8 @@ interface Validated { fun validate() { val errors = validator() if (errors.isNotEmpty()) { - logger.error(RED + "Exceptions when parsing command line arguments:") - logger.error(errors.joinToString("\n") + RESET) + logger.error(ShellConstants.RED + "Exceptions when parsing command line arguments:") + logger.error(errors.joinToString("\n") + ShellConstants.RESET) CommandLine(this).usage(System.err) exitProcess(ExitCodes.FAILURE) } @@ -55,6 +53,11 @@ object CordaSystemUtils { fun getOsName(): String = System.getProperty(OS_NAME) } +object ShellConstants { + const val RED = "\u001B[31m" + const val RESET = "\u001B[0m" +} + fun CordaCliWrapper.start(args: Array) { this.args = args @@ -66,27 +69,47 @@ fun CordaCliWrapper.start(args: Array) { cmd.registerConverter(Path::class.java) { Paths.get(it).toAbsolutePath().normalize() } cmd.commandSpec.name(alias) cmd.commandSpec.usageMessage().description(description) + cmd.commandSpec.parser().collectErrors(true) try { - val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) { Help.Ansi.ON } else { Help.Ansi.AUTO } - val results = cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(defaultAnsiMode), - DefaultExceptionHandler>().useErr(System.err).useAnsi(defaultAnsiMode), - *args) - // If an error code has been returned, use this and exit - results?.firstOrNull()?.let { - if (it is Int) { - exitProcess(it) - } else { + val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) { + Help.Ansi.ON + } else { + Help.Ansi.AUTO + } + + val results = cmd.parse(*args) + val app = cmd.getCommand() + if (cmd.isUsageHelpRequested) { + cmd.usage(System.out, defaultAnsiMode) + exitProcess(ExitCodes.SUCCESS) + } + if (cmd.isVersionHelpRequested) { + cmd.printVersionHelp(System.out, defaultAnsiMode) + exitProcess(ExitCodes.SUCCESS) + } + if (app.installShellExtensionsParser.installShellExtensions) { + System.out.println("Install shell extensions: ${app.installShellExtensionsParser.installShellExtensions}") + // ignore any parsing errors and run the program + exitProcess(app.call()) + } + val allErrors = results.flatMap { it.parseResult?.errors() ?: emptyList() } + if (allErrors.any()) { + val parameterExceptions = allErrors.asSequence().filter { it is ParameterException } + if (parameterExceptions.any()) { + System.err.println("${ShellConstants.RED}${parameterExceptions.map{ it.message }.joinToString()}${ShellConstants.RESET}") + parameterExceptions.filter { it is UnmatchedArgumentException}.forEach { (it as UnmatchedArgumentException).printSuggestions(System.out) } + usage(cmd, System.out, defaultAnsiMode) exitProcess(ExitCodes.FAILURE) } + throw allErrors.first() } - // If no results returned, picocli ran something without invoking the main program, e.g. --help or --version, so exit successfully - exitProcess(ExitCodes.SUCCESS) - } catch (e: ExecutionException) { + exitProcess(app.call()) + } catch (e: Exception) { val throwable = e.cause ?: e if (this.verbose) { throwable.printStackTrace() } else { - System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}") + System.err.println("${ShellConstants.RED}${throwable.rootMessage ?: "Use --verbose for more details"}${ShellConstants.RESET}") } exitProcess(ExitCodes.FAILURE) } @@ -126,7 +149,7 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Cal var loggingLevel: Level = Level.INFO @Mixin - private lateinit var installShellExtensionsParser: InstallShellExtensionsParser + lateinit var installShellExtensionsParser: InstallShellExtensionsParser // This needs to be called before loggers (See: NodeStartup.kt:51 logger called by lazy, initLogging happens before). // Node's logging is more rich. In corda configurations two properties, defaultLoggingLevel and consoleLogLevel, are usually used. diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt index 86430dadf3..dfecae05ac 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt @@ -2,7 +2,7 @@ package net.corda.demobench import javafx.scene.image.Image import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.demobench.views.DemoBenchView import net.corda.serialization.internal.AMQP_P2P_CONTEXT @@ -56,7 +56,7 @@ class DemoBench : App(DemoBenchView::class) { } private fun initialiseSerialization() { - nodeSerializationEnv = SerializationEnvironmentImpl( + nodeSerializationEnv = SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPClientSerializationScheme(emptyList())) }, diff --git a/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/serialization/SerializationHelper.kt b/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/serialization/SerializationHelper.kt index ce978e4131..d0efa8d492 100644 --- a/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/serialization/SerializationHelper.kt +++ b/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/serialization/SerializationHelper.kt @@ -1,9 +1,10 @@ package net.corda.bootstrapper.serialization -import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT +import net.corda.node.serialization.kryo.KryoCheckpointSerializer import net.corda.serialization.internal.AMQP_P2P_CONTEXT import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT import net.corda.serialization.internal.SerializationFactoryImpl @@ -14,14 +15,16 @@ class SerializationEngine { synchronized(this) { if (nodeSerializationEnv == null) { val classloader = this::class.java.classLoader - nodeSerializationEnv = SerializationEnvironmentImpl( + nodeSerializationEnv = SerializationEnvironment.with( SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme(emptyList())) }, p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), rpcServerContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader), - checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader) + + checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader), + checkpointSerializer = KryoCheckpointSerializer ) } }