diff --git a/bridge/src/main/kotlin/net/corda/bridge/internal/AMQPFirewallSerializationScheme.kt b/bridge/src/main/kotlin/net/corda/bridge/internal/AMQPFirewallSerializationScheme.kt index 2f172e1fe1..f28125a074 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/internal/AMQPFirewallSerializationScheme.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/internal/AMQPFirewallSerializationScheme.kt @@ -6,24 +6,24 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationCustomSerializer import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.amqpMagic import java.util.concurrent.ConcurrentHashMap class AMQPFirewallSerializationScheme( cordappCustomSerializers: Set>, - serializerFactoriesForContexts: MutableMap, SerializerFactory> + serializerFactoriesForContexts: AccessOrderLinkedHashMap, SerializerFactory> ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { - constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) + constructor(cordapps: List) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 }) override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() } - override fun rpcServerSerializerFactory(context: SerializationContext) : SerializerFactory { + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() } - override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) - = (magic == amqpMagic && target == SerializationContext.UseCase.P2P) + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) = (magic == amqpMagic && target == SerializationContext.UseCase.P2P) } \ No newline at end of file diff --git a/build.gradle b/build.gradle index fb7c98782e..a208dc801e 100644 --- a/build.gradle +++ b/build.gradle @@ -436,20 +436,20 @@ artifactory { defaults { // Root project applies the plugin (for this block) but does not need to be published - if(project != rootProject) { + if (project != rootProject) { publications(project.extensions.publish.name()) } } } } -task generateApi(type: net.corda.plugins.GenerateApi){ +task generateApi(type: net.corda.plugins.GenerateApi) { baseName = "api-corda" } // This exists to reduce CI build time when the envvar is set (can save up to 40 minutes) -if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) { - if(file('corda-docs-only-build').exists()) { +if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) { + if (file('corda-docs-only-build').exists()) { logger.info("Tests are disabled due to presence of file 'corda-docs-only-build' in the project root") } else { logger.info("Tests are disabled due to the presence of envvar CORDA_DOCS_ONLY_BUILD") @@ -461,7 +461,7 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI } it.afterEvaluate { - if(it.tasks.findByName("integrationTest") != null) { + if (it.tasks.findByName("integrationTest") != null) { integrationTest { exclude '*/**' } @@ -469,7 +469,7 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI } it.afterEvaluate { - if(it.tasks.findByName("smokeTest") != null) { + if (it.tasks.findByName("smokeTest") != null) { smokeTest { exclude '*/**' } diff --git a/buildSrc/jarfilter/kotlin-metadata/build.gradle b/buildSrc/jarfilter/kotlin-metadata/build.gradle index 561cea19a1..b2b685dca1 100644 --- a/buildSrc/jarfilter/kotlin-metadata/build.gradle +++ b/buildSrc/jarfilter/kotlin-metadata/build.gradle @@ -26,7 +26,7 @@ def originalJar = configurations.proguard.files.find { it.name.startsWith("kotli import proguard.gradle.ProGuardTask task metadata(type: ProGuardTask) { - injars originalJar, filter: '!META-INF/native/**' + injars originalJar, filter: 'META-INF/MANIFEST.MF,META-INF/metadata*.kotlin_module,**.class' outjars "$buildDir/libs/${project.name}-${kotlin_version}.jar" libraryjars "$javaHome/lib/rt.jar" @@ -49,7 +49,10 @@ task metadata(type: ProGuardTask) { dontnote keep 'class org.jetbrains.kotlin.load.java.JvmAnnotationNames { *; }' - keep 'class org.jetbrains.kotlin.metadata.** { *; }', includedescriptorclasses: true + keep 'class org.jetbrains.kotlin.metadata.ProtoBuf { *; }', includedescriptorclasses: true + keep 'class org.jetbrains.kotlin.metadata.ProtoBuf$* { *; }', includedescriptorclasses: true + keep 'class org.jetbrains.kotlin.metadata.deserialization.** { *; }', includedescriptorclasses: true + keep 'class org.jetbrains.kotlin.metadata.jvm.** { *; }', includedescriptorclasses: true keep 'class org.jetbrains.kotlin.protobuf.** { *; }', includedescriptorclasses: true } def metadataJar = metadata.outputs.files.singleFile diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt index 54ca3ad542..5f49d0422a 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt @@ -2,16 +2,21 @@ package net.corda.gradle.jarfilter import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.Flags.* import org.jetbrains.kotlin.metadata.deserialization.NameResolver import org.jetbrains.kotlin.metadata.deserialization.TypeTable import org.jetbrains.kotlin.metadata.deserialization.returnType import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf import org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.objectweb.asm.Opcodes.ACC_SYNTHETIC import java.util.* +private const val DEFAULT_CONSTRUCTOR_MARKER = "ILkotlin/jvm/internal/DefaultConstructorMarker;" private const val DUMMY_PASSES = 1 +private val DECLARES_DEFAULT_VALUE_MASK: Int = DECLARES_DEFAULT_VALUE.toFlags(true).inv() + internal abstract class Element(val name: String, val descriptor: String) { private var lifetime: Int = DUMMY_PASSES @@ -36,6 +41,7 @@ internal class MethodElement(name: String, descriptor: String, val access: Int = private val suffix: String val visibleName: String + val signature: String = name + descriptor init { val idx = name.indexOf('$') @@ -44,6 +50,14 @@ internal class MethodElement(name: String, descriptor: String, val access: Int = } fun isKotlinSynthetic(vararg tags: String): Boolean = (access and ACC_SYNTHETIC) != 0 && tags.contains(suffix) + fun asKotlinNonDefaultConstructor(): MethodElement? { + val markerIdx = descriptor.indexOf(DEFAULT_CONSTRUCTOR_MARKER) + return if (markerIdx >= 0) { + MethodElement(name, descriptor.removeRange(markerIdx, markerIdx + DEFAULT_CONSTRUCTOR_MARKER.length)) + } else { + null + } + } } @@ -107,4 +121,24 @@ internal fun JvmProtoBuf.JvmPropertySignature.toFieldElement(property: ProtoBuf. } return FieldElement(nameResolver.getString(nameId), descriptor) -} \ No newline at end of file +} + +/** + * Rewrites metadata for constructor parameters. + */ +internal fun ProtoBuf.Constructor.Builder.updateValueParameters( + updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter +): ProtoBuf.Constructor.Builder { + for (idx in 0 until valueParameterList.size) { + setValueParameter(idx, updater(valueParameterList[idx])) + } + return this +} + +internal fun ProtoBuf.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.ValueParameter { + return if (DECLARES_DEFAULT_VALUE.get(flags)) { + toBuilder().setFlags(flags and DECLARES_DEFAULT_VALUE_MASK).build() + } else { + this + } +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt index 7c33ac4dd7..e6e2089381 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt @@ -248,7 +248,7 @@ class FilterTransformer private constructor ( logger.info("-- also identified property or typealias {},{} for deletion", method.visibleName, extensionType) } } - } else if (stubAnnotations.contains(descriptor) && (method.access and (ACC_ABSTRACT or ACC_SYNTHETIC)) == 0) { + } else if (stubAnnotations.contains(descriptor) && (method.access and ACC_ABSTRACT) == 0) { if (stubbedMethods.add(method)) { logger.info("- Identified method {}{} for stubbing out", method.name, method.descriptor) } diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt index 871cf4bff3..73d0db24ca 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt @@ -95,17 +95,30 @@ internal abstract class MetadataTransformer( private fun filterConstructors(): Int = deletedConstructors.count(::filterConstructor) private fun filterConstructor(deleted: MethodElement): Boolean { + /* + * Constructors with the default parameter marker are synthetic and DO NOT have + * entries in the metadata. So we construct an element for the "primary" one + * that it was synthesised for, and which we DO expect to find. + */ + val deletedPrimary = deleted.asKotlinNonDefaultConstructor() + for (idx in 0 until constructors.size) { val constructor = constructors[idx] val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructor, nameResolver, typeTable) - if (signature == deleted.name + deleted.descriptor) { + if (signature == deleted.signature) { if (IS_SECONDARY.get(constructor.flags)) { - logger.info("-- removing constructor: {}{}", deleted.name, deleted.descriptor) + logger.info("-- removing constructor: {}", deleted.signature) } else { logger.warn("Removing primary constructor: {}{}", className, deleted.descriptor) } constructors.removeAt(idx) return true + } else if (signature == deletedPrimary?.signature) { + constructors[idx] = constructor.toBuilder() + .updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue) + .build() + logger.info("-- removing default parameter values: {}", signature) + return true } } return false @@ -118,8 +131,8 @@ internal abstract class MetadataTransformer( val function = functions[idx] if (nameResolver.getString(function.name) == deleted.name) { val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable) - if (signature == deleted.name + deleted.descriptor) { - logger.info("-- removing function: {}{}", deleted.name, deleted.descriptor) + if (signature == deleted.signature) { + logger.info("-- removing function: {}", deleted.signature) functions.removeAt(idx) return true } @@ -157,7 +170,7 @@ internal abstract class MetadataTransformer( private fun deleteExtra(func: MethodElement) { if (!deletedFunctions.contains(func)) { - logger.info("-- identified extra method {}{} for deletion", func.name, func.descriptor) + logger.info("-- identified extra method {} for deletion", func.signature) handleExtraMethod(func) filterFunction(func) } @@ -244,12 +257,11 @@ internal class ClassMetadataTransformer( override val typeAliases = mutableList(message.typeAliasList) override fun rebuild(): ProtoBuf.Class = message.toBuilder().apply { + clearConstructor().addAllConstructor(constructors) + if (nestedClassNames.size != nestedClassNameCount) { clearNestedClassName().addAllNestedClassName(nestedClassNames) } - if (constructors.size != constructorCount) { - clearConstructor().addAllConstructor(constructors) - } if (functions.size != functionCount) { clearFunction().addAllFunction(functions) } diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseDeleteConstructorTest.kt similarity index 62% rename from buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseConstructorTest.kt rename to buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseDeleteConstructorTest.kt index f45a26d082..2c8c442410 100644 --- a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseConstructorTest.kt +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseDeleteConstructorTest.kt @@ -16,12 +16,13 @@ import kotlin.jvm.kotlin import kotlin.reflect.full.primaryConstructor import kotlin.test.assertFailsWith -class SanitiseConstructorTest { +class SanitiseDeleteConstructorTest { companion object { + private const val COMPLEX_CONSTRUCTOR_CLASS = "net.corda.gradle.HasOverloadedComplexConstructorToDelete" private const val COUNT_INITIAL_OVERLOADED = 1 private const val COUNT_INITIAL_MULTIPLE = 2 private val testProjectDir = TemporaryFolder() - private val testProject = JarFilterProject(testProjectDir, "sanitise-constructor") + private val testProject = JarFilterProject(testProjectDir, "sanitise-delete-constructor") @ClassRule @JvmField @@ -32,13 +33,13 @@ class SanitiseConstructorTest { @Test fun deleteOverloadedLongConstructor() = checkClassWithLongParameter( - "net.corda.gradle.HasOverloadedLongConstructor", + "net.corda.gradle.HasOverloadedLongConstructorToDelete", COUNT_INITIAL_OVERLOADED ) @Test fun deleteMultipleLongConstructor() = checkClassWithLongParameter( - "net.corda.gradle.HasMultipleLongConstructors", + "net.corda.gradle.HasMultipleLongConstructorsToDelete", COUNT_INITIAL_MULTIPLE ) @@ -57,9 +58,9 @@ class SanitiseConstructorTest { val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER) - newInstance().also { - assertEquals(0, it.longData()) - } + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0) + assertThat(newInstance().longData()).isEqualTo(0) } } @@ -75,6 +76,7 @@ class SanitiseConstructorTest { val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER) + assertNull("no-arg constructor exists", kotlin.noArgConstructor) assertFailsWith { getDeclaredConstructor() } } } @@ -82,13 +84,13 @@ class SanitiseConstructorTest { @Test fun deleteOverloadedIntConstructor() = checkClassWithIntParameter( - "net.corda.gradle.HasOverloadedIntConstructor", + "net.corda.gradle.HasOverloadedIntConstructorToDelete", COUNT_INITIAL_OVERLOADED ) @Test fun deleteMultipleIntConstructor() = checkClassWithIntParameter( - "net.corda.gradle.HasMultipleIntConstructors", + "net.corda.gradle.HasMultipleIntConstructorsToDelete", COUNT_INITIAL_MULTIPLE ) @@ -107,10 +109,9 @@ class SanitiseConstructorTest { val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER) - //assertThat("", constructors, hasItem(isConstructor("")) - newInstance().also { - assertEquals(0, it.intData()) - } + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0) + assertThat(newInstance().intData()).isEqualTo(0) } } @@ -126,6 +127,7 @@ class SanitiseConstructorTest { val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER) + assertNull("no-arg constructor exists", kotlin.noArgConstructor) assertFailsWith { getDeclaredConstructor() } } } @@ -133,13 +135,13 @@ class SanitiseConstructorTest { @Test fun deleteOverloadedStringConstructor() = checkClassWithStringParameter( - "net.corda.gradle.HasOverloadedStringConstructor", + "net.corda.gradle.HasOverloadedStringConstructorToDelete", COUNT_INITIAL_OVERLOADED ) @Test fun deleteMultipleStringConstructor() = checkClassWithStringParameter( - "net.corda.gradle.HasMultipleStringConstructors", + "net.corda.gradle.HasMultipleStringConstructorsToDelete", COUNT_INITIAL_MULTIPLE ) @@ -158,9 +160,9 @@ class SanitiseConstructorTest { val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE) - newInstance().also { - assertEquals(DEFAULT_MESSAGE, it.stringData()) - } + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE) + assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE) } } @@ -176,8 +178,57 @@ class SanitiseConstructorTest { val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE) + assertNull("no-arg constructor exists", kotlin.noArgConstructor) assertFailsWith { getDeclaredConstructor() } } } } -} + + @Test + fun deleteOverloadedComplexConstructor() { + val complexConstructor = isConstructor(COMPLEX_CONSTRUCTOR_CLASS, Int::class, String::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(COMPLEX_CONSTRUCTOR_CLASS).apply { + kotlin.constructors.apply { + assertThat("(Int,String) not found", this, hasItem(complexConstructor)) + assertEquals(1, this.size) + } + + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + primary.call(NUMBER, MESSAGE).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(NUMBER) + } + + primary.callBy(mapOf(primary.parameters[1] to MESSAGE)).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(0) + } + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(0) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(COMPLEX_CONSTRUCTOR_CLASS).apply { + kotlin.constructors.apply { + assertThat("(Int,String) not found", this, hasItem(complexConstructor)) + assertEquals(1, this.size) + } + + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + primary.call(NUMBER, MESSAGE).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(NUMBER) + } + + assertThat(assertFailsWith { primary.callBy(mapOf(primary.parameters[1] to MESSAGE)) }) + .hasMessageContaining("No argument provided for a required parameter") + assertFailsWith { getDeclaredConstructor(String::class.java) } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseStubConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseStubConstructorTest.kt new file mode 100644 index 0000000000..d0f55cb00b --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseStubConstructorTest.kt @@ -0,0 +1,250 @@ +package net.corda.gradle.jarfilter + +import net.corda.gradle.jarfilter.matcher.* +import net.corda.gradle.unwanted.HasInt +import net.corda.gradle.unwanted.HasLong +import net.corda.gradle.unwanted.HasString +import org.assertj.core.api.Assertions.* +import org.hamcrest.core.IsCollectionContaining.hasItem +import org.junit.Assert.* +import org.junit.ClassRule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.rules.TestRule +import java.lang.reflect.InvocationTargetException +import kotlin.jvm.kotlin +import kotlin.reflect.full.primaryConstructor +import kotlin.test.assertFailsWith + +class SanitiseStubConstructorTest { + companion object { + private const val COMPLEX_CONSTRUCTOR_CLASS = "net.corda.gradle.HasOverloadedComplexConstructorToStub" + private const val COUNT_OVERLOADED = 1 + private const val COUNT_MULTIPLE = 2 + private val testProjectDir = TemporaryFolder() + private val testProject = JarFilterProject(testProjectDir, "sanitise-stub-constructor") + + @ClassRule + @JvmField + val rules: TestRule = RuleChain + .outerRule(testProjectDir) + .around(testProject) + } + + @Test + fun stubOverloadedLongConstructor() = checkClassWithLongParameter( + "net.corda.gradle.HasOverloadedLongConstructorToStub", + COUNT_OVERLOADED + ) + + @Test + fun stubMultipleLongConstructor() = checkClassWithLongParameter( + "net.corda.gradle.HasMultipleLongConstructorsToStub", + COUNT_MULTIPLE + ) + + private fun checkClassWithLongParameter(longClass: String, constructorCount: Int) { + val longConstructor = isConstructor(longClass, Long::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(longClass).apply { + getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { + assertEquals(BIG_NUMBER, it.longData()) + } + kotlin.constructors.apply { + assertThat("(J) not found", this, hasItem(longConstructor)) + assertEquals(constructorCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER) + + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0) + assertThat(newInstance().longData()).isEqualTo(0) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(longClass).apply { + getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { + assertEquals(BIG_NUMBER, it.longData()) + } + kotlin.constructors.apply { + assertThat("(J) not found", this, hasItem(longConstructor)) + assertEquals(constructorCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER) + + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(assertFailsWith { noArg.callBy(emptyMap()) }.targetException) + .isInstanceOf(UnsupportedOperationException::class.java) + .hasMessage("Method has been deleted") + assertThat(assertFailsWith { newInstance() }) + .hasMessage("Method has been deleted") + } + } + } + + @Test + fun stubOverloadedIntConstructor() = checkClassWithIntParameter( + "net.corda.gradle.HasOverloadedIntConstructorToStub", + COUNT_OVERLOADED + ) + + @Test + fun stubMultipleIntConstructor() = checkClassWithIntParameter( + "net.corda.gradle.HasMultipleIntConstructorsToStub", + COUNT_MULTIPLE + ) + + private fun checkClassWithIntParameter(intClass: String, constructorCount: Int) { + val intConstructor = isConstructor(intClass, Int::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(intClass).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { + assertEquals(NUMBER, it.intData()) + } + kotlin.constructors.apply { + assertThat("(I) not found", this, hasItem(intConstructor)) + assertEquals(constructorCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER) + + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0) + assertThat(newInstance().intData()).isEqualTo(0) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(intClass).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { + assertEquals(NUMBER, it.intData()) + } + kotlin.constructors.apply { + assertThat("(I) not found", this, hasItem(intConstructor)) + assertEquals(constructorCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER) + + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(assertFailsWith { noArg.callBy(emptyMap()) }.targetException) + .isInstanceOf(UnsupportedOperationException::class.java) + .hasMessage("Method has been deleted") + assertThat(assertFailsWith { newInstance() }) + .hasMessage("Method has been deleted") + } + } + } + + @Test + fun stubOverloadedStringConstructor() = checkClassWithStringParameter( + "net.corda.gradle.HasOverloadedStringConstructorToStub", + COUNT_OVERLOADED + ) + + @Test + fun stubMultipleStringConstructor() = checkClassWithStringParameter( + "net.corda.gradle.HasMultipleStringConstructorsToStub", + COUNT_MULTIPLE + ) + + private fun checkClassWithStringParameter(stringClass: String, constructorCount: Int) { + val stringConstructor = isConstructor(stringClass, String::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(stringClass).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertEquals(MESSAGE, it.stringData()) + } + kotlin.constructors.apply { + assertThat("(String) not found", this, hasItem(stringConstructor)) + assertEquals(constructorCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE) + + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE) + assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(stringClass).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertEquals(MESSAGE, it.stringData()) + } + kotlin.constructors.apply { + assertThat("(String) not found", this, hasItem(stringConstructor)) + assertEquals(constructorCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE) + + val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing") + assertThat(assertFailsWith { noArg.callBy(emptyMap()) }.targetException) + .isInstanceOf(UnsupportedOperationException::class.java) + .hasMessage("Method has been deleted") + assertThat(assertFailsWith { newInstance() }) + .hasMessage("Method has been deleted") + } + } + } + + @Test + fun stubOverloadedComplexConstructor() { + val complexConstructor = isConstructor(COMPLEX_CONSTRUCTOR_CLASS, Int::class, String::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(COMPLEX_CONSTRUCTOR_CLASS).apply { + kotlin.constructors.apply { + assertThat("(Int,String) not found", this, hasItem(complexConstructor)) + assertEquals(1, this.size) + } + + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + primary.call(NUMBER, MESSAGE).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(NUMBER) + } + + primary.callBy(mapOf(primary.parameters[1] to MESSAGE)).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(0) + } + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(0) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(COMPLEX_CONSTRUCTOR_CLASS).apply { + kotlin.constructors.apply { + assertThat("(Int,String) not found", this, hasItem(complexConstructor)) + assertEquals(1, this.size) + } + + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + primary.call(NUMBER, MESSAGE).also { complex -> + assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE) + assertThat((complex as HasInt).intData()).isEqualTo(NUMBER) + } + + assertThat(assertFailsWith { primary.callBy(mapOf(primary.parameters[1] to MESSAGE)) }.targetException) + .isInstanceOf(kotlin.UnsupportedOperationException::class.java) + .hasMessage("Method has been deleted") + assertThat(assertFailsWith { getDeclaredConstructor(String::class.java).newInstance(MESSAGE) }.targetException) + .hasMessage("Method has been deleted") + } + } + } + +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt index b0bc5a6bfb..b27809b5db 100644 --- a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt @@ -14,6 +14,8 @@ import java.nio.file.Paths import java.util.stream.Collectors.* import java.util.zip.ZipFile import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter const val DEFAULT_MESSAGE = "" const val MESSAGE = "Goodbye, Cruel World!" @@ -65,6 +67,9 @@ fun arrayOfJunk(size: Int) = ByteArray(size).apply { } } +val KClass.noArgConstructor: KFunction? + get() = constructors.firstOrNull { it.parameters.all(KParameter::isOptional) } + @Throws(MalformedURLException::class) fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader) diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-constructor/kotlin/net/corda/gradle/HasOverloadedConstructors.kt b/buildSrc/jarfilter/src/test/resources/sanitise-constructor/kotlin/net/corda/gradle/HasOverloadedConstructors.kt deleted file mode 100644 index 434244befc..0000000000 --- a/buildSrc/jarfilter/src/test/resources/sanitise-constructor/kotlin/net/corda/gradle/HasOverloadedConstructors.kt +++ /dev/null @@ -1,34 +0,0 @@ -@file:Suppress("UNUSED") -package net.corda.gradle - -import net.corda.gradle.jarfilter.DeleteMe -import net.corda.gradle.unwanted.* - -private const val DEFAULT_MESSAGE = "" - -class HasOverloadedStringConstructor @JvmOverloads @DeleteMe constructor(private val message: String = DEFAULT_MESSAGE) : HasString { - override fun stringData(): String = message -} - -class HasOverloadedLongConstructor @JvmOverloads @DeleteMe constructor(private val data: Long = 0) : HasLong { - override fun longData(): Long = data -} - -class HasOverloadedIntConstructor @JvmOverloads @DeleteMe constructor(private val data: Int = 0) : HasInt { - override fun intData(): Int = data -} - -class HasMultipleStringConstructors(private val message: String) : HasString { - @DeleteMe constructor() : this(DEFAULT_MESSAGE) - override fun stringData(): String = message -} - -class HasMultipleLongConstructors(private val data: Long) : HasLong { - @DeleteMe constructor() : this(0) - override fun longData(): Long = data -} - -class HasMultipleIntConstructors(private val data: Int) : HasInt { - @DeleteMe constructor() : this(0) - override fun intData(): Int = data -} diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-constructor/build.gradle b/buildSrc/jarfilter/src/test/resources/sanitise-delete-constructor/build.gradle similarity index 85% rename from buildSrc/jarfilter/src/test/resources/sanitise-constructor/build.gradle rename to buildSrc/jarfilter/src/test/resources/sanitise-delete-constructor/build.gradle index c1cf56e499..6fc9780cf8 100644 --- a/buildSrc/jarfilter/src/test/resources/sanitise-constructor/build.gradle +++ b/buildSrc/jarfilter/src/test/resources/sanitise-delete-constructor/build.gradle @@ -8,7 +8,7 @@ sourceSets { main { kotlin { srcDir files( - '../resources/test/sanitise-constructor/kotlin', + '../resources/test/sanitise-delete-constructor/kotlin', '../resources/test/annotations/kotlin' ) } @@ -21,7 +21,7 @@ dependencies { } jar { - baseName = 'sanitise-constructor' + baseName = 'sanitise-delete-constructor' } import net.corda.gradle.jarfilter.JarFilterTask diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-delete-constructor/kotlin/net/corda/gradle/HasOverloadedConstructorsToDelete.kt b/buildSrc/jarfilter/src/test/resources/sanitise-delete-constructor/kotlin/net/corda/gradle/HasOverloadedConstructorsToDelete.kt new file mode 100644 index 0000000000..b5b257ab99 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/sanitise-delete-constructor/kotlin/net/corda/gradle/HasOverloadedConstructorsToDelete.kt @@ -0,0 +1,48 @@ +@file:JvmName("HasOverloadedConstructorsToDelete") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.* + +private const val DEFAULT_MESSAGE = "" + +class HasOverloadedStringConstructorToDelete @JvmOverloads @DeleteMe constructor(private val message: String = DEFAULT_MESSAGE) : HasString { + override fun stringData(): String = message +} + +class HasOverloadedLongConstructorToDelete @JvmOverloads @DeleteMe constructor(private val data: Long = 0) : HasLong { + override fun longData(): Long = data +} + +class HasOverloadedIntConstructorToDelete @JvmOverloads @DeleteMe constructor(private val data: Int = 0) : HasInt { + override fun intData(): Int = data +} + +/** + * This case is complex because: + * - The primary constructor has two parameters. + * - The first constructor parameter has a default value. + * - The second constructor parameter is mandatory. + */ +class HasOverloadedComplexConstructorToDelete @JvmOverloads @DeleteMe constructor(private val data: Int = 0, private val message: String) + : HasInt, HasString +{ + override fun stringData(): String = message + override fun intData(): Int = data +} + +class HasMultipleStringConstructorsToDelete(private val message: String) : HasString { + @DeleteMe constructor() : this(DEFAULT_MESSAGE) + override fun stringData(): String = message +} + +class HasMultipleLongConstructorsToDelete(private val data: Long) : HasLong { + @DeleteMe constructor() : this(0) + override fun longData(): Long = data +} + +class HasMultipleIntConstructorsToDelete(private val data: Int) : HasInt { + @DeleteMe constructor() : this(0) + override fun intData(): Int = data +} diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-stub-constructor/build.gradle b/buildSrc/jarfilter/src/test/resources/sanitise-stub-constructor/build.gradle new file mode 100644 index 0000000000..0eef51c207 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/sanitise-stub-constructor/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/sanitise-stub-constructor/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'sanitise-stub-constructor' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forStub = ["net.corda.gradle.jarfilter.StubMeOut"] + forSanitise = ["net.corda.gradle.jarfilter.StubMeOut"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-stub-constructor/kotlin/net/corda/gradle/HasOverloadedConstructorsToStub.kt b/buildSrc/jarfilter/src/test/resources/sanitise-stub-constructor/kotlin/net/corda/gradle/HasOverloadedConstructorsToStub.kt new file mode 100644 index 0000000000..47e4882957 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/sanitise-stub-constructor/kotlin/net/corda/gradle/HasOverloadedConstructorsToStub.kt @@ -0,0 +1,48 @@ +@file:JvmName("HasOverloadedConstructorsToStub") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut +import net.corda.gradle.unwanted.* + +private const val DEFAULT_MESSAGE = "" + +class HasOverloadedStringConstructorToStub @JvmOverloads @StubMeOut constructor(private val message: String = DEFAULT_MESSAGE) : HasString { + override fun stringData(): String = message +} + +class HasOverloadedLongConstructorToStub @JvmOverloads @StubMeOut constructor(private val data: Long = 0) : HasLong { + override fun longData(): Long = data +} + +class HasOverloadedIntConstructorToStub @JvmOverloads @StubMeOut constructor(private val data: Int = 0) : HasInt { + override fun intData(): Int = data +} + +/** + * This case is complex because: + * - The primary constructor has two parameters. + * - The first constructor parameter has a default value. + * - The second constructor parameter is mandatory. + */ +class HasOverloadedComplexConstructorToStub @JvmOverloads @StubMeOut constructor(private val data: Int = 0, private val message: String) + : HasInt, HasString +{ + override fun stringData(): String = message + override fun intData(): Int = data +} + +class HasMultipleStringConstructorsToStub(private val message: String) : HasString { + @StubMeOut constructor() : this(DEFAULT_MESSAGE) + override fun stringData(): String = message +} + +class HasMultipleLongConstructorsToStub(private val data: Long) : HasLong { + @StubMeOut constructor() : this(0) + override fun longData(): Long = data +} + +class HasMultipleIntConstructorsToStub(private val data: Int) : HasInt { + @StubMeOut constructor() : this(0) + override fun intData(): Int = data +} 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 1f82119356..aa0e6618aa 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 @@ -10,6 +10,7 @@ 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 +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.amqpMagic import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer @@ -21,12 +22,12 @@ import java.util.concurrent.ConcurrentHashMap */ class AMQPClientSerializationScheme( cordappCustomSerializers: Set>, - serializerFactoriesForContexts: MutableMap, SerializerFactory> + serializerFactoriesForContexts: AccessOrderLinkedHashMap, SerializerFactory> ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { - constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) + constructor(cordapps: List) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 }) @Suppress("UNUSED") - constructor() : this(emptySet(), ConcurrentHashMap()) + constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 }) companion object { /** Call from main only. */ @@ -52,7 +53,7 @@ class AMQPClientSerializationScheme( } override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { - return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply { + return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply { register(RpcClientObservableSerializer) register(RpcClientCordaFutureSerializer(this)) register(RxNotificationSerializer(this)) diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt index b91e8ebf01..8c9e056bfc 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt @@ -30,10 +30,10 @@ object IdentitySyncFlow { * recipient, enabling them to verify that identity. */ // TODO: Can this be triggered automatically from [SendTransactionFlow]? - class Send(val otherSideSessions: Set, + class Send @JvmOverloads constructor(val otherSideSessions: Set, val tx: WireTransaction, - override val progressTracker: ProgressTracker) : FlowLogic() { - constructor(otherSide: FlowSession, tx: WireTransaction) : this(setOf(otherSide), tx, tracker()) + override val progressTracker: ProgressTracker = tracker()) : FlowLogic() { + constructor(otherSide: FlowSession, tx: WireTransaction) : this(setOf(otherSide), tx) companion object { object SYNCING_IDENTITIES : ProgressTracker.Step("Syncing identities") diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index cceb9d930c..a298578595 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -184,6 +184,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { defaultTasks "determinise" determinise.finalizedBy metafix metafix.finalizedBy checkDeterminism +assemble.dependsOn checkDeterminism def deterministicJar = metafix.outputs.files.singleFile artifacts { diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt index 184f7f72d9..05c8c3bc5c 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt @@ -2,12 +2,13 @@ package net.corda.deterministic.common import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext -import net.corda.core.serialization.SerializationContext.UseCase.* +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._contextSerializationEnv import net.corda.serialization.internal.* import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.amqpMagic import org.junit.rules.TestRule @@ -21,13 +22,13 @@ class LocalSerializationRule(private val label: String) : TestRule { private companion object { private val AMQP_P2P_CONTEXT = SerializationContextImpl( - amqpMagic, - LocalSerializationRule::class.java.classLoader, - GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), - emptyMap(), - true, - P2P, - null + amqpMagic, + LocalSerializationRule::class.java.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + P2P, + null ) } @@ -59,7 +60,7 @@ class LocalSerializationRule(private val label: String) : TestRule { private fun createTestSerializationEnv(): SerializationEnvironmentImpl { val factory = SerializationFactoryImpl(mutableMapOf()).apply { - registerScheme(AMQPSerializationScheme(emptySet(), mutableMapOf())) + registerScheme(AMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap(128))) } return object : SerializationEnvironmentImpl(factory, AMQP_P2P_CONTEXT) { override fun toString() = "testSerializationEnv($label)" @@ -67,8 +68,8 @@ class LocalSerializationRule(private val label: String) : TestRule { } private class AMQPSerializationScheme( - cordappCustomSerializers: Set>, - serializerFactoriesForContexts: MutableMap, SerializerFactory> + cordappCustomSerializers: Set>, + serializerFactoriesForContexts: AccessOrderLinkedHashMap, SerializerFactory> ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index e21cc456dd..b31096674f 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -184,7 +184,6 @@ interface SerializationContext { /** * Helper method to return a new context based on this context with the deserialization class loader changed. */ - @DeleteForDJVM fun withClassLoader(classLoader: ClassLoader): SerializationContext /** diff --git a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt index 2f28d692a2..e1183be6b7 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationCustomSerializer import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer import java.util.concurrent.ConcurrentHashMap @@ -16,11 +17,11 @@ import java.util.concurrent.ConcurrentHashMap */ class AMQPServerSerializationScheme( cordappCustomSerializers: Set>, - serializerFactoriesForContexts: MutableMap, SerializerFactory> + serializerFactoriesForContexts: AccessOrderLinkedHashMap, SerializerFactory> ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { - constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) + constructor(cordapps: List) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 }) - constructor() : this(emptySet(), ConcurrentHashMap()) + constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 }) override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() diff --git a/node/src/test/kotlin/net/corda/node/internal/serialization/RoundTripObservableSerializerTests.kt b/node/src/test/kotlin/net/corda/node/internal/serialization/RoundTripObservableSerializerTests.kt index 0620f0f115..7289a9a959 100644 --- a/node/src/test/kotlin/net/corda/node/internal/serialization/RoundTripObservableSerializerTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/serialization/RoundTripObservableSerializerTests.kt @@ -13,6 +13,7 @@ import net.corda.node.internal.serialization.testutils.serializationContext import net.corda.node.serialization.amqp.RpcServerObservableSerializer import net.corda.node.services.messaging.ObservableSubscription import net.corda.nodeapi.RPCApi +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.DeserializationInput import net.corda.serialization.internal.amqp.SerializationOutput import org.apache.activemq.artemis.api.core.SimpleString @@ -59,7 +60,7 @@ class RoundTripObservableSerializerTests { @Test fun roundTripTest1() { val serializationScheme = AMQPRoundTripRPCSerializationScheme( - serializationContext, emptySet(), ConcurrentHashMap()) + serializationContext, emptySet(), AccessOrderLinkedHashMap { 128 }) // Fake up a message ID, needs to be used on both "sides". The server setting it in the subscriptionMap, // the client as a property of the deserializer which, in the actual RPC client, is pulled off of diff --git a/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt b/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt index 82fb3eaa70..c397920197 100644 --- a/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt +++ b/node/src/test/kotlin/net/corda/node/internal/serialization/testutils/AMQPTestSerialiationScheme.kt @@ -11,6 +11,7 @@ import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext /** @@ -22,7 +23,7 @@ import net.corda.client.rpc.internal.ObservableContext as ClientObservableContex class AMQPRoundTripRPCSerializationScheme( private val serializationContext: SerializationContext, cordappCustomSerializers: Set>, - serializerFactoriesForContexts: MutableMap, SerializerFactory>) + serializerFactoriesForContexts: AccessOrderLinkedHashMap, SerializerFactory>) : AbstractAMQPSerializationScheme( cordappCustomSerializers, serializerFactoriesForContexts ) { diff --git a/samples/bank-of-corda-demo/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt b/samples/bank-of-corda-demo/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt new file mode 100644 index 0000000000..058f56fe66 --- /dev/null +++ b/samples/bank-of-corda-demo/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.internal.amqp.custom + +import net.corda.serialization.internal.amqp.SerializerFactory +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.nullValue +import org.junit.Assert +import org.junit.Test +import org.mockito.Mockito +import java.util.* + +class OptionalSerializerTest { + @Test + fun `should convert optional with item to proxy`() { + val opt = Optional.of("GenericTestString") + val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt) + Assert.assertThat(proxy.item, `is`("GenericTestString")) + } + + @Test + fun `should convert optional without item to empty proxy`() { + val opt = Optional.ofNullable(null) + val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt) + Assert.assertThat(proxy.item, `is`(nullValue())) + } + + @Test + fun `should convert proxy without item to empty optional `() { + val proxy = OptionalSerializer.OptionalProxy(null) + val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy) + Assert.assertThat(opt.isPresent, `is`(false)) + } + + @Test + fun `should convert proxy with item to empty optional `() { + val proxy = OptionalSerializer.OptionalProxy("GenericTestString") + val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy) + Assert.assertThat(opt.get(), `is`("GenericTestString")) + } +} \ No newline at end of file diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index e8bd17131b..8d77449637 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -58,6 +58,7 @@ task patchSerialization(type: Zip, dependsOn: serializationJarTask) { exclude 'net/corda/serialization/internal/DefaultWhitelist*' exclude 'net/corda/serialization/internal/amqp/AMQPSerializerFactories*' exclude 'net/corda/serialization/internal/amqp/AMQPStreams*' + exclude 'net/corda/serialization/internal/amqp/AMQPSerializationThreadContext*' } reproducibleFileOrder = true @@ -166,6 +167,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { defaultTasks "determinise" determinise.finalizedBy metafix metafix.finalizedBy checkDeterminism +assemble.dependsOn checkDeterminism def deterministicJar = metafix.outputs.files.singleFile artifacts { diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationThreadContext.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationThreadContext.kt new file mode 100644 index 0000000000..723dda762d --- /dev/null +++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationThreadContext.kt @@ -0,0 +1,6 @@ +@file:JvmName("AMQPSerializationThreadContext") +package net.corda.serialization.internal.amqp + +fun getContextClassLoader(): ClassLoader { + return ClassLoader.getSystemClassLoader() +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt index 68cbb51dbf..4d37506b4c 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt @@ -21,10 +21,13 @@ import net.corda.core.internal.objectOrNewInstance import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence -import net.corda.serialization.internal.* +import net.corda.core.utilities.contextLogger +import net.corda.serialization.internal.CordaSerializationMagic +import net.corda.serialization.internal.DefaultWhitelist +import net.corda.serialization.internal.MutableClassWhitelist +import net.corda.serialization.internal.SerializationScheme import java.lang.reflect.Modifier import java.util.* -import java.util.concurrent.ConcurrentHashMap val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic @@ -46,18 +49,20 @@ interface SerializerFactoryFactory { @KeepForDJVM abstract class AbstractAMQPSerializationScheme( - private val cordappCustomSerializers: Set>, - private val serializerFactoriesForContexts: MutableMap, SerializerFactory>, - val sff: SerializerFactoryFactory = createSerializerFactoryFactory() + private val cordappCustomSerializers: Set>, + private val serializerFactoriesForContexts: AccessOrderLinkedHashMap, SerializerFactory>, + val sff: SerializerFactoryFactory = createSerializerFactoryFactory() ) : SerializationScheme { @DeleteForDJVM - constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) + constructor(cordapps: List) : this(cordapps.customSerializers, AccessOrderLinkedHashMap(128)) // TODO: This method of initialisation for the Whitelist and plugin serializers will have to change // when we have per-cordapp contexts and dynamic app reloading but for now it's the easiest way companion object { const val SCAN_SPEC_PROP_NAME = "amqp.custom.serialization.scanSpec" + private val logger = contextLogger() + private val serializationWhitelists: List by lazy { ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist } @@ -66,24 +71,27 @@ abstract class AbstractAMQPSerializationScheme( val scanSpec: String? = System.getProperty(SCAN_SPEC_PROP_NAME) if (scanSpec == null) { + logger.debug("scanSpec not set, not scanning for Custom Serializers") emptyList() } else { + logger.debug("scanSpec = \"$scanSpec\", scanning for Custom Serializers") scanClasspathForSerializers(scanSpec) } } @StubOutForDJVM private fun scanClasspathForSerializers(scanSpec: String): List> = - this::class.java.classLoader.let { cl -> - FastClasspathScanner(scanSpec).addClassLoader(cl).scan() - .getNamesOfClassesImplementing(SerializationCustomSerializer::class.java) - .map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) } - .filterNot { Modifier.isAbstract(it.modifiers) } - .map { it.kotlin.objectOrNewInstance() } - } + this::class.java.classLoader.let { cl -> + FastClasspathScanner(scanSpec).addClassLoader(cl).scan() + .getNamesOfClassesImplementing(SerializationCustomSerializer::class.java) + .map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) } + .filterNot { Modifier.isAbstract(it.modifiers) } + .map { it.kotlin.objectOrNewInstance() } + } @DeleteForDJVM - val List.customSerializers get() = flatMap { it.serializationCustomSerializers }.toSet() + val List.customSerializers + get() = flatMap { it.serializationCustomSerializers }.toSet() } // Parameter "context" is unused directly but passed in by reflection. Removing it will cause failures. @@ -105,6 +113,7 @@ abstract class AbstractAMQPSerializationScheme( register(net.corda.serialization.internal.amqp.custom.ZoneIdSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer(this)) + register(net.corda.serialization.internal.amqp.custom.OptionalSerializer(this)) register(net.corda.serialization.internal.amqp.custom.YearSerializer(this)) register(net.corda.serialization.internal.amqp.custom.YearMonthSerializer(this)) register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this)) @@ -131,6 +140,7 @@ abstract class AbstractAMQPSerializationScheme( factory.registerExternal(CorDappCustomSerializer(customSerializer, factory)) } } else { + logger.debug("Custom Serializer list loaded - not scanning classpath") cordappCustomSerializers.forEach { customSerializer -> factory.registerExternal(CorDappCustomSerializer(customSerializer, factory)) } @@ -157,32 +167,41 @@ abstract class AbstractAMQPSerializationScheme( protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory // Not used as a simple direct import to facilitate testing - open val publicKeySerializer : CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer + open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer private fun getSerializerFactory(context: SerializationContext): SerializerFactory { - return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { - when (context.useCase) { - SerializationContext.UseCase.Checkpoint -> - throw IllegalStateException("AMQP should not be used for checkpoint serialization.") - SerializationContext.UseCase.RPCClient -> - rpcClientSerializerFactory(context) - SerializationContext.UseCase.RPCServer -> - rpcServerSerializerFactory(context) - else -> sff.make(context) - }.also { - registerCustomSerializers(context, it) + return synchronized(serializerFactoriesForContexts) { + serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { + when (context.useCase) { + SerializationContext.UseCase.Checkpoint -> + throw IllegalStateException("AMQP should not be used for checkpoint serialization.") + SerializationContext.UseCase.RPCClient -> + rpcClientSerializerFactory(context) + SerializationContext.UseCase.RPCServer -> + rpcServerSerializerFactory(context) + else -> sff.make(context) + }.also { + registerCustomSerializers(context, it) + } } } } override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - val serializerFactory = getSerializerFactory(context) + var contextToUse = context + if (context.useCase == SerializationContext.UseCase.RPCClient) { + contextToUse = context.withClassLoader(getContextClassLoader()) + } + val serializerFactory = getSerializerFactory(contextToUse) return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context) } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - val serializerFactory = getSerializerFactory(context) - + var contextToUse = context + if (context.useCase == SerializationContext.UseCase.RPCClient) { + contextToUse = context.withClassLoader(getContextClassLoader()) + } + val serializerFactory = getSerializerFactory(contextToUse) return SerializationOutput(serializerFactory).serialize(obj, context) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationThreadContext.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationThreadContext.kt new file mode 100644 index 0000000000..3f8bedfbc4 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationThreadContext.kt @@ -0,0 +1,6 @@ +@file:JvmName("AMQPSerializationThreadContext") +package net.corda.serialization.internal.amqp + +fun getContextClassLoader(): ClassLoader { + return Thread.currentThread().contextClassLoader +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AccessOrderLinkedHashMap.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AccessOrderLinkedHashMap.kt new file mode 100644 index 0000000000..d7e50afa4d --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AccessOrderLinkedHashMap.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM + +@KeepForDJVM +class AccessOrderLinkedHashMap(private val maxSize: Int) : LinkedHashMap(16, 0.75f, true) { + constructor(loader: () -> Int) : this(loader.invoke()) + + override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean { + return this.size > maxSize + } +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CorDappCustomSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CorDappCustomSerializer.kt index bcab1bc8ff..09e2c47da9 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CorDappCustomSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CorDappCustomSerializer.kt @@ -10,6 +10,7 @@ package net.corda.serialization.internal.amqp +import com.google.common.reflect.TypeToken import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationCustomSerializer @@ -55,8 +56,10 @@ const val PROXY_TYPE = 1 */ class CorDappCustomSerializer( private val serializer: SerializationCustomSerializer<*, *>, - factory: SerializerFactory) : AMQPSerializer, SerializerFor { + factory: SerializerFactory +) : AMQPSerializer, SerializerFor { override val revealSubclassesInSchema: Boolean get() = false + private val types = serializer::class.supertypes.filter { it.jvmErasure == SerializationCustomSerializer::class } .flatMap { it.arguments } .map { it.type!!.javaType } @@ -95,6 +98,12 @@ class CorDappCustomSerializer( ) = uncheckedCast, SerializationCustomSerializer>( serializer).fromProxy(uncheckedCast(proxySerializer.readObject(obj, schemas, input, context)))!! - override fun isSerializerFor(clazz: Class<*>) = clazz == type + /** + * For 3rd party plugin serializers we are going to exist on exact type matching. i.e. we will + * not support base class serializers for derivedtypes + */ + override fun isSerializerFor(clazz: Class<*>) : Boolean { + return type.asClass()?.let { TypeToken.of(it) == TypeToken.of(clazz) } ?: false + } } 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 ff19337f20..5750281f22 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 @@ -286,7 +286,7 @@ internal fun propertiesForSerializationFromConstructor( "in the Java compiler. Alternately, provide a proxy serializer " + "(SerializationCustomSerializer) if recompiling isn't an option") - Pair(PrivatePropertyReader(field, type), field.genericType) + Pair(PrivatePropertyReader(field, type), resolveTypeVariables(field.genericType, type)) } this += PropertyAccessorConstructor( 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 7d7198950f..46c937e20b 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 @@ -42,6 +42,8 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ * @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal * use case is an [EvolutionSerializer] type is returned. However, in some scenarios, primarily testing, this * can be altered to fit the requirements of the test. + * @property onlyCustomSerializers used for testing, when set will cause the factory to throw a + * [NotSerializableException] if it cannot find a registered custom serializer for a given type */ // TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency // TODO: maybe support for caching of serialized form of some core types for performance @@ -64,13 +66,15 @@ open class SerializerFactory( private val serializersByType: MutableMap>, val serializersByDescriptor: MutableMap>, private val customSerializers: MutableList, - val transformsCache: MutableMap>> + val transformsCache: MutableMap>>, + private val onlyCustomSerializers: Boolean = false ) { @DeleteForDJVM constructor(whitelist: ClassWhitelist, classCarpenter: ClassCarpenter, evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), - fingerPrinter: FingerPrinter = SerializerFingerPrinter() + fingerPrinter: FingerPrinter = SerializerFingerPrinter(), + onlyCustomSerializers: Boolean = false ) : this( whitelist, classCarpenter, @@ -79,7 +83,8 @@ open class SerializerFactory( ConcurrentHashMap(), ConcurrentHashMap(), CopyOnWriteArrayList(), - ConcurrentHashMap() + ConcurrentHashMap(), + onlyCustomSerializers ) @DeleteForDJVM @@ -87,8 +92,14 @@ open class SerializerFactory( carpenterClassLoader: ClassLoader, lenientCarpenter: Boolean = false, evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), - fingerPrinter: FingerPrinter = SerializerFingerPrinter() - ) : this(whitelist, ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter) + fingerPrinter: FingerPrinter = SerializerFingerPrinter(), + onlyCustomSerializers: Boolean = false + ) : this( + whitelist, + ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), + evolutionSerializerGetter, + fingerPrinter, + onlyCustomSerializers) init { fingerPrinter.setOwner(this) @@ -139,7 +150,7 @@ open class SerializerFactory( } } Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> { - logger.debug("class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " + logger.info("class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " + "declaredType=${declaredType.typeName} " + "isEnum=${declaredType::class.java.isEnum}") @@ -246,6 +257,7 @@ open class SerializerFactory( * that expects to find getters and a constructor with a parameter for each property. */ open fun register(customSerializer: CustomSerializer) { + logger.info ("action=\"Registering custom serializer\", class=\"${customSerializer.type}\"") if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) { customSerializers += customSerializer serializersByDescriptor[customSerializer.typeDescriptor] = customSerializer @@ -256,6 +268,7 @@ open class SerializerFactory( } fun registerExternal(customSerializer: CorDappCustomSerializer) { + logger.info ("action=\"Registering external serializer\", class=\"${customSerializer.type}\"") if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) { customSerializers += customSerializer serializersByDescriptor[customSerializer.typeDescriptor] = customSerializer @@ -338,8 +351,12 @@ open class SerializerFactory( return get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type) } - private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer = serializersByType.computeIfAbsent(type) { - logger.debug("class=${clazz.simpleName}, type=$type is a composite type") + private fun makeClassSerializer( + clazz: Class<*>, + type: Type, + declaredType: Type + ): AMQPSerializer = serializersByType.computeIfAbsent(type) { + logger.debug { "class=${clazz.simpleName}, type=$type is a composite type" } if (clazz.isSynthetic) { // Explicitly ban synthetic classes, we have no way of recreating them when deserializing. This also // captures Lambda expressions and other anonymous functions @@ -348,6 +365,9 @@ open class SerializerFactory( AMQPPrimitiveSerializer(clazz) } else { findCustomSerializer(clazz, declaredType) ?: run { + if (onlyCustomSerializers) { + throw NotSerializableException("Only allowing custom serializers") + } if (type.isArray()) { // Don't need to check the whitelist since each element will come back through the whitelisting process. if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this) @@ -373,9 +393,15 @@ open class SerializerFactory( for (customSerializer in customSerializers) { if (customSerializer.isSerializerFor(clazz)) { val declaredSuperClass = declaredType.asClass()?.superclass + + return if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass) - || !customSerializer.revealSubclassesInSchema) { + || !customSerializer.revealSubclassesInSchema + ) { + logger.info ("action=\"Using custom serializer\", class=${clazz.typeName}, " + + "declaredType=${declaredType.typeName}") + @Suppress("UNCHECKED_CAST") customSerializer as? AMQPSerializer } else { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializer.kt new file mode 100644 index 0000000000..632cc4c511 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializer.kt @@ -0,0 +1,24 @@ +package net.corda.serialization.internal.amqp.custom + +import net.corda.core.KeepForDJVM +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import java.time.OffsetTime +import java.util.* + +/** + * A serializer for [OffsetTime] that uses a proxy object to write out the time and zone offset. + */ +class OptionalSerializer(factory: SerializerFactory) : CustomSerializer.Proxy, OptionalSerializer.OptionalProxy>(Optional::class.java, OptionalProxy::class.java, factory) { + + public override fun toProxy(obj: java.util.Optional<*>): OptionalProxy { + return OptionalProxy(obj.orElse(null)) + } + + public override fun fromProxy(proxy: OptionalProxy): Optional<*> { + return Optional.ofNullable(proxy.item) + } + + @KeepForDJVM + data class OptionalProxy(val item: Any?) +} \ No newline at end of file diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/DummyOptional.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/DummyOptional.java new file mode 100644 index 0000000000..9bd1f65142 --- /dev/null +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/DummyOptional.java @@ -0,0 +1,37 @@ +package net.corda.serialization.internal.amqp; + +import net.corda.core.serialization.CordaSerializable; + +import java.util.Objects; + +@CordaSerializable +public class DummyOptional { + + private final T item; + + public boolean isPresent() { + return item != null; + } + + public T get() { + return item; + } + + public DummyOptional(T item) { + this.item = item; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DummyOptional that = (DummyOptional) o; + return Objects.equals(item, that.item); + } + + @Override + public int hashCode() { + + return Objects.hash(item); + } +} \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AbstractAMQPSerializationSchemeTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AbstractAMQPSerializationSchemeTest.kt new file mode 100644 index 0000000000..8c38cbeb26 --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/AbstractAMQPSerializationSchemeTest.kt @@ -0,0 +1,76 @@ +package net.corda.serialization.internal.amqp + +import net.corda.core.serialization.ClassWhitelist +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.deserialize +import net.corda.core.utilities.ByteSequence +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.CordaSerializationMagic +import net.corda.serialization.internal.SerializationContextImpl +import net.corda.serialization.internal.amqp.testutils.serializationProperties +import net.corda.testing.core.SerializationEnvironmentRule +import org.hamcrest.CoreMatchers +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.Matchers +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import java.net.URLClassLoader +import java.util.concurrent.ThreadLocalRandom +import java.util.stream.IntStream + +class AbstractAMQPSerializationSchemeTest { + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + @Test + fun `number of cached factories must be bounded by maxFactories`() { + val genesisContext = SerializationContextImpl( + ByteSequence.of(byteArrayOf('c'.toByte(), 'o'.toByte(), 'r'.toByte(), 'd'.toByte(), 'a'.toByte(), 0.toByte(), 0.toByte(), 1.toByte())), + ClassLoader.getSystemClassLoader(), + AllWhitelist, + serializationProperties, + false, + SerializationContext.UseCase.RPCClient, + null) + + + val factory = TestSerializerFactory(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader) + val maxFactories = 512 + val backingMap = AccessOrderLinkedHashMap, SerializerFactory>({ maxFactories }) + val scheme = object : AbstractAMQPSerializationScheme(emptySet(), backingMap, createSerializerFactoryFactory()) { + override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { + return factory + } + + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { + return factory + } + + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return true + } + + } + + + IntStream.range(0, 2048).parallel().forEach { + val context = if (ThreadLocalRandom.current().nextBoolean()) { + genesisContext.withClassLoader(URLClassLoader(emptyArray())) + } else { + genesisContext + } + val testString = "TEST${ThreadLocalRandom.current().nextInt()}" + val serialized = scheme.serialize(testString, context) + val deserialized = serialized.deserialize(context = context, serializationFactory = testSerialization.serializationFactory) + Assert.assertThat(testString, `is`(deserialized)) + Assert.assertThat(backingMap.size, `is`(Matchers.lessThanOrEqualTo(maxFactories))) + } + Assert.assertThat(backingMap.size, CoreMatchers.`is`(Matchers.lessThanOrEqualTo(maxFactories))) + } +} + + + diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/CorDappSerializerTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/CorDappSerializerTests.kt index 0ffd3a27ac..c85c6bf368 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/CorDappSerializerTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/CorDappSerializerTests.kt @@ -10,21 +10,29 @@ package net.corda.serialization.internal.amqp -import org.junit.Test import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationCustomSerializer -import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope -import net.corda.serialization.internal.amqp.testutils.deserialize -import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema -import net.corda.serialization.internal.amqp.testutils.serialize -import net.corda.serialization.internal.amqp.testutils.testDefaultFactory +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.testutils.* import org.assertj.core.api.Assertions +import org.junit.Test import java.io.NotSerializableException import kotlin.test.assertEquals - class CorDappSerializerTests { - data class NeedsProxy (val a: String) + data class NeedsProxy(val a: String) + + private fun proxyFactory( + serializers: List> + ) = SerializerFactory( + AllWhitelist, + ClassLoader.getSystemClassLoader(), + onlyCustomSerializers = true + ).apply { + serializers.forEach { + registerExternal(CorDappCustomSerializer(it, this)) + } + } class NeedsProxyProxySerializer : SerializationCustomSerializer { data class Proxy(val proxy_a_: String) @@ -35,7 +43,7 @@ class CorDappSerializerTests { // Standard proxy serializer used internally, here for comparison purposes class InternalProxySerializer(factory: SerializerFactory) : - CustomSerializer.Proxy ( + CustomSerializer.Proxy( NeedsProxy::class.java, InternalProxySerializer.Proxy::class.java, factory) { @@ -58,14 +66,14 @@ class CorDappSerializerTests { val msg = "help" - proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory)) - internalProxyFactory.register (InternalProxySerializer(internalProxyFactory)) + proxyFactory.registerExternal(CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory)) + internalProxyFactory.register(InternalProxySerializer(internalProxyFactory)) val needsProxy = NeedsProxy(msg) - val bAndSProxy = SerializationOutput(proxyFactory).serializeAndReturnSchema (needsProxy) - val bAndSInternal = SerializationOutput(internalProxyFactory).serializeAndReturnSchema (needsProxy) - val bAndSDefault = SerializationOutput(defaultFactory).serializeAndReturnSchema (needsProxy) + val bAndSProxy = SerializationOutput(proxyFactory).serializeAndReturnSchema(needsProxy) + val bAndSInternal = SerializationOutput(internalProxyFactory).serializeAndReturnSchema(needsProxy) + val bAndSDefault = SerializationOutput(defaultFactory).serializeAndReturnSchema(needsProxy) val objFromDefault = DeserializationInput(defaultFactory).deserializeAndReturnEnvelope(bAndSDefault.obj) val objFromInternal = DeserializationInput(internalProxyFactory).deserializeAndReturnEnvelope(bAndSInternal.obj) @@ -78,14 +86,14 @@ class CorDappSerializerTests { @Test fun proxiedTypeIsNested() { - data class A (val a: Int, val b: NeedsProxy) + data class A(val a: Int, val b: NeedsProxy) val factory = testDefaultFactory() - factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + factory.registerExternal(CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) val tv1 = 100 val tv2 = "pants schmants" - val bAndS = SerializationOutput(factory).serializeAndReturnSchema (A(tv1, NeedsProxy(tv2))) + val bAndS = SerializationOutput(factory).serializeAndReturnSchema(A(tv1, NeedsProxy(tv2))) val objFromDefault = DeserializationInput(factory).deserializeAndReturnEnvelope(bAndS.obj) @@ -95,7 +103,7 @@ class CorDappSerializerTests { @Test fun testWithWhitelistNotAllowed() { - data class A (val a: Int, val b: NeedsProxy) + data class A(val a: Int, val b: NeedsProxy) class WL : ClassWhitelist { private val allowedClasses = emptySet() @@ -104,7 +112,7 @@ class CorDappSerializerTests { } val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader()) - factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + factory.registerExternal(CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) val tv1 = 100 val tv2 = "pants schmants" @@ -115,7 +123,7 @@ class CorDappSerializerTests { @Test fun testWithWhitelistAllowed() { - data class A (val a: Int, val b: NeedsProxy) + data class A(val a: Int, val b: NeedsProxy) class WL : ClassWhitelist { private val allowedClasses = setOf( @@ -126,7 +134,7 @@ class CorDappSerializerTests { } val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader()) - factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + factory.registerExternal(CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) val tv1 = 100 val tv2 = "pants schmants" @@ -141,7 +149,7 @@ class CorDappSerializerTests { // custom serializer bypasses the whitelist @Test fun testWithWhitelistAllowedOuterOnly() { - data class A (val a: Int, val b: NeedsProxy) + data class A(val a: Int, val b: NeedsProxy) class WL : ClassWhitelist { // explicitly don't add NeedsProxy @@ -151,7 +159,7 @@ class CorDappSerializerTests { } val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader()) - factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + factory.registerExternal(CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) val tv1 = 100 val tv2 = "pants schmants" @@ -161,4 +169,136 @@ class CorDappSerializerTests { assertEquals(tv1, obj.a) assertEquals(tv2, obj.b.a) } + + data class NeedsProxyGen(val a: T) + + class NeedsProxyGenProxySerializer : + SerializationCustomSerializer, NeedsProxyGenProxySerializer.Proxy> { + data class Proxy(val proxy_a_: Any?) + + override fun fromProxy(proxy: Proxy) = NeedsProxyGen(proxy.proxy_a_) + override fun toProxy(obj: NeedsProxyGen<*>) = Proxy(obj.a) + } + + // Tests CORDA-1747 + @Test + fun proxiedGeneric() { + val proxyFactory = proxyFactory(listOf(NeedsProxyGenProxySerializer())) + + val msg = "help" + + + val blob = SerializationOutput(proxyFactory).serialize(NeedsProxyGen(msg)) + val objFromProxy = DeserializationInput(proxyFactory).deserialize(blob) + + assertEquals(msg, objFromProxy.a) + } + + // Need an interface to restrict the generic to in the following test + interface Bound { + fun wibbleIt(): String + } + + // test class to be serialized whose generic arg is restricted to instances + // of the Bound interface declared above + data class NeedsProxyGenBounded(val a: T) + + // Proxy for our test class + class NeedsProxyGenBoundedProxySerializer : + SerializationCustomSerializer, + NeedsProxyGenBoundedProxySerializer.Proxy> { + data class Proxy(val proxy_a_: Bound) + + override fun fromProxy(proxy: Proxy) = NeedsProxyGenBounded(proxy.proxy_a_) + override fun toProxy(obj: NeedsProxyGenBounded<*>) = Proxy(obj.a) + } + + // Since we need a value for our test class that implements the interface + // we're restricting its generic property to, this is that class. + data class HasWibble(val a: String) : Bound { + override fun wibbleIt() = "wibble it, just a little bit!." + } + + // Because we're enforcing all classes have proxy serializers we + // have to implement this to avoid the factory erroneously failing + class HasWibbleProxy : + SerializationCustomSerializer { + data class Proxy(val proxy_a_: String) + + override fun fromProxy(proxy: Proxy) = HasWibble(proxy.proxy_a_) + override fun toProxy(obj: HasWibble) = Proxy(obj.a) + } + + // Tests CORDA-1747 - Finally the actual bound generics test, on failure it will throw + @Test + fun proxiedBoundedGeneric() { + val proxyFactory = proxyFactory(listOf(NeedsProxyGenBoundedProxySerializer(), HasWibbleProxy())) + + val blob = SerializationOutput(proxyFactory).serialize(NeedsProxyGenBounded(HasWibble("A"))) + val objFromProxy = DeserializationInput(proxyFactory).deserialize(blob) + + assertEquals("A", objFromProxy.a.a) + } + + data class NeedsProxyGenContainer(val a: List) + + class NeedsProxyGenContainerProxySerializer : + SerializationCustomSerializer, + NeedsProxyGenContainerProxySerializer.Proxy> { + data class Proxy(val proxy_a_: List<*>) + + override fun fromProxy(proxy: Proxy) = NeedsProxyGenContainer(proxy.proxy_a_) + override fun toProxy(obj: NeedsProxyGenContainer<*>) = Proxy(obj.a) + } + + // Tests CORDA-1747 + @Test + fun proxiedGenericContainer() { + val proxyFactory = proxyFactory(listOf(NeedsProxyGenContainerProxySerializer())) + + val blob1 = SerializationOutput(proxyFactory).serialize(NeedsProxyGenContainer(listOf(1, 2, 3))) + val obj1 = DeserializationInput(proxyFactory).deserialize(blob1) + + assertEquals(listOf(1, 2, 3), obj1.a) + + val blob2 = SerializationOutput(proxyFactory).serialize(NeedsProxyGenContainer(listOf("1", "2", "3"))) + val obj2 = DeserializationInput(proxyFactory).deserialize(blob2) + + assertEquals(listOf("1", "2", "3"), obj2.a) + + val blob3 = SerializationOutput(proxyFactory).serialize(NeedsProxyGenContainer(listOf("1", 2, "3"))) + val obj3 = DeserializationInput(proxyFactory).deserialize(blob3) + + assertEquals(listOf("1", 2, "3"), obj3.a) + } + + open class Base(val a: T) + class Derived(a: T, val b: String) : Base(a) + + class BaseProxy : + SerializationCustomSerializer, BaseProxy.Proxy> { + data class Proxy(val proxy_a_: Any?) + + override fun fromProxy(proxy: Proxy) = Base(proxy.proxy_a_) + override fun toProxy(obj: Base<*>) = Proxy(obj.a) + } + + class DerivedProxy : + SerializationCustomSerializer, DerivedProxy.Proxy> { + data class Proxy(val proxy_a_: Any?, val proxy_b_: String) + + override fun fromProxy(proxy: Proxy) = Derived(proxy.proxy_a_, proxy.proxy_b_) + override fun toProxy(obj: Derived<*>) = Proxy(obj.a, obj.b) + } + + // Tests CORDA-1747 + @Test + fun proxiedInheritableGenerics() { + val proxyFactory = proxyFactory(listOf(BaseProxy(), DerivedProxy())) + + val blob1 = SerializationOutput(proxyFactory).serialize(Base(100L)) + DeserializationInput(proxyFactory).deserialize(blob1) + val blob2 = SerializationOutput(proxyFactory).serialize(Derived(100L, "Hey pants")) + DeserializationInput(proxyFactory).deserialize(blob2) + } } \ No newline at end of file diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/OptionalSerializationTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/OptionalSerializationTests.kt new file mode 100644 index 0000000000..10b154863f --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/OptionalSerializationTests.kt @@ -0,0 +1,36 @@ +package net.corda.serialization.internal.amqp + +import net.corda.serialization.internal.AllWhitelist +import net.corda.serialization.internal.amqp.custom.OptionalSerializer +import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput +import net.corda.serialization.internal.amqp.testutils.deserialize +import net.corda.serialization.internal.amqp.testutils.testDefaultFactory +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo +import org.junit.Assert +import org.junit.Test +import java.util.* + +class OptionalSerializationTests { + + @Test + fun setupEnclosedSerializationTest() { + @Test + fun `java optionals should serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(OptionalSerializer(factory)) + val obj = Optional.ofNullable("YES") + val bytes = TestSerializationOutput(true, factory).serialize(obj) + val deserializerFactory = testDefaultFactory().apply { + register(OptionalSerializer(this)) + } + + val deserialized = DeserializationInput(factory).deserialize(bytes) + val deserialized2 = DeserializationInput(deserializerFactory).deserialize(bytes) + Assert.assertThat(deserialized, `is`(equalTo(deserialized2))) + Assert.assertThat(obj, `is`(equalTo(deserialized2))) + } + + `java optionals should serialize`() + } +} \ No newline at end of file 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 f39ef6264a..841623eff4 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 @@ -520,6 +520,12 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())) } + @Test + fun `generics from java are supported`() { + val obj = DummyOptional("YES") + serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())) + } + @Test fun `test throwables serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -979,6 +985,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi class Spike private constructor(val a: String) { constructor() : this("a") + override fun equals(other: Any?): Boolean = other is Spike && other.a == this.a override fun hashCode(): Int = a.hashCode() } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationSchemaTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationSchemaTests.kt index 7f71b60ae6..a5f4925f24 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationSchemaTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationSchemaTests.kt @@ -2,11 +2,11 @@ package net.corda.serialization.internal.amqp import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence -import net.corda.serialization.internal.* import net.corda.serialization.internal.BuiltInExceptionsWhitelist +import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.GlobalTransientClassWhiteList +import net.corda.serialization.internal.SerializationContextImpl import org.junit.Test -import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals // Make sure all serialization calls in this test don't get stomped on by anything else @@ -46,7 +46,7 @@ class TestSerializerFactoryFactory : SerializerFactoryFactoryImpl() { } } -class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptySet(), ConcurrentHashMap(), TestSerializerFactoryFactory()) { +class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap { 128 }, TestSerializerFactoryFactory()) { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() } @@ -91,7 +91,7 @@ class SerializationSchemaTests { val c = C(1) val testSerializationFactory = TestSerializationFactory() - val expectedCustomSerializerCount = 40 + val expectedCustomSerializerCount = 41 assertEquals(0, testFactory.registerCount) c.serialize(testSerializationFactory, TESTING_CONTEXT) 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 025bebdfe0..9f300fd9e8 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 @@ -17,9 +17,14 @@ import java.io.NotSerializableException import java.nio.file.StandardCopyOption.REPLACE_EXISTING fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + fun testDefaultFactoryNoEvolution(): SerializerFactory { - return SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(), evolutionSerializerGetter = EvolutionSerializerGetterTesting()) + return SerializerFactory( + AllWhitelist, + ClassLoader.getSystemClassLoader(), + evolutionSerializerGetter = EvolutionSerializerGetterTesting()) } + fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()) class TestSerializationOutput(