From 36f02ff4b46435d23b1627f0a85526aa1ae364ce Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Wed, 28 Mar 2018 08:40:28 +0100 Subject: [PATCH 1/3] [CORDA-1267]: Making Corda's JPA classes non-final and Serializable. (#2882) * Making Corda's JPA classes non-final and Serializable. * Making Corda's JPA classes non-final and Serializable. * Making Corda's JPA classes non-final and Serializable. * Making Corda's JPA classes non-final and Serializable. --- .ci/api-current.txt | 40 +++++++++---------- build.gradle | 10 +++++ .../net/corda/core/schemas/PersistentTypes.kt | 2 +- .../node/internal/schemas/NodeInfoSchema.kt | 7 ++-- .../services/events/NodeSchedulerService.kt | 3 +- .../identity/PersistentIdentityService.kt | 5 ++- .../keys/PersistentKeyManagementService.kt | 3 +- .../services/messaging/P2PMessagingClient.kt | 5 ++- .../persistence/DBCheckpointStorage.kt | 3 +- .../DBTransactionMappingStorage.kt | 3 +- .../persistence/DBTransactionStorage.kt | 3 +- .../NodePropertiesPersistentStore.kt | 3 +- .../PersistentUniquenessProvider.kt | 5 ++- .../transactions/RaftUniquenessProvider.kt | 3 +- .../upgrade/ContractUpgradeServiceImpl.kt | 3 +- .../services/schema/NodeSchemaServiceTest.kt | 3 +- 16 files changed, 62 insertions(+), 39 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 1e7e6da933..1aa2494645 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -2842,27 +2842,27 @@ public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core. @javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState public () public (Set, net.corda.core.identity.AbstractParty, long, net.corda.core.identity.AbstractParty, byte[]) - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty getIssuer() - @org.jetbrains.annotations.NotNull public final byte[] getIssuerRef() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty getOwner() + @org.jetbrains.annotations.NotNull public net.corda.core.identity.AbstractParty getIssuer() + @org.jetbrains.annotations.NotNull public byte[] getIssuerRef() + @org.jetbrains.annotations.NotNull public net.corda.core.identity.AbstractParty getOwner() @org.jetbrains.annotations.Nullable public Set getParticipants() - public final long getQuantity() - public final void setIssuer(net.corda.core.identity.AbstractParty) - public final void setIssuerRef(byte[]) - public final void setOwner(net.corda.core.identity.AbstractParty) + public long getQuantity() + public void setIssuer(net.corda.core.identity.AbstractParty) + public void setIssuerRef(byte[]) + public void setOwner(net.corda.core.identity.AbstractParty) public void setParticipants(Set) - public final void setQuantity(long) + public void setQuantity(long) ## @javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState public () public (Set, String, UUID) public (net.corda.core.contracts.UniqueIdentifier, Set) - @org.jetbrains.annotations.Nullable public final String getExternalId() + @org.jetbrains.annotations.Nullable public String getExternalId() @org.jetbrains.annotations.Nullable public Set getParticipants() - @org.jetbrains.annotations.NotNull public final UUID getUuid() - public final void setExternalId(String) + @org.jetbrains.annotations.NotNull public UUID getUuid() + public void setExternalId(String) public void setParticipants(Set) - public final void setUuid(UUID) + public void setUuid(UUID) ## public class net.corda.core.schemas.MappedSchema extends java.lang.Object public (Class, int, Iterable) @@ -2874,10 +2874,10 @@ public class net.corda.core.schemas.MappedSchema extends java.lang.Object @javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable public () public (net.corda.core.schemas.PersistentStateRef) - @org.jetbrains.annotations.Nullable public final net.corda.core.schemas.PersistentStateRef getStateRef() - public final void setStateRef(net.corda.core.schemas.PersistentStateRef) + @org.jetbrains.annotations.Nullable public net.corda.core.schemas.PersistentStateRef getStateRef() + public void setStateRef(net.corda.core.schemas.PersistentStateRef) ## -@javax.persistence.Embeddable public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable public () public (String, Integer) public (net.corda.core.contracts.StateRef) @@ -2885,18 +2885,18 @@ public class net.corda.core.schemas.MappedSchema extends java.lang.Object @org.jetbrains.annotations.Nullable public final Integer component2() @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.PersistentStateRef copy(String, Integer) public boolean equals(Object) - @org.jetbrains.annotations.Nullable public final Integer getIndex() - @org.jetbrains.annotations.Nullable public final String getTxId() + @org.jetbrains.annotations.Nullable public Integer getIndex() + @org.jetbrains.annotations.Nullable public String getTxId() public int hashCode() - public final void setIndex(Integer) - public final void setTxId(String) + public void setIndex(Integer) + public void setTxId(String) public String toString() ## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.schemas.PersistentState generateMappedObject(net.corda.core.schemas.MappedSchema) @org.jetbrains.annotations.NotNull public abstract Iterable supportedSchemas() ## -public interface net.corda.core.schemas.StatePersistable +public interface net.corda.core.schemas.StatePersistable extends java.io.Serializable ## public interface net.corda.core.serialization.ClassWhitelist public abstract boolean hasListed(Class) diff --git a/build.gradle b/build.gradle index cd31b768a7..8b7f906d38 100644 --- a/build.gradle +++ b/build.gradle @@ -93,6 +93,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath "net.corda.plugins:publish-utils:$gradle_plugins_version" classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version" @@ -140,6 +141,15 @@ allprojects { apply plugin: 'java' apply plugin: 'jacoco' apply plugin: 'org.owasp.dependencycheck' + apply plugin: 'kotlin-allopen' + + allOpen { + annotations( + "javax.persistence.Entity", + "javax.persistence.Embeddable", + "javax.persistence.MappedSuperclass" + ) + } dependencyCheck { suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index cd55da4982..fbf9b3ccf9 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -69,4 +69,4 @@ data class PersistentStateRef( /** * Marker interface to denote a persistable Corda state entity that will always have a transaction id and index */ -interface StatePersistable \ No newline at end of file +interface StatePersistable : Serializable \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt b/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt index 4bf0bd16c4..3e0386b149 100644 --- a/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt +++ b/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt @@ -10,6 +10,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.persistence.NodePropertiesPersistentStore +import java.io.Serializable import javax.persistence.* object NodeInfoSchema @@ -52,7 +53,7 @@ object NodeInfoSchemaV1 : MappedSchema( */ @Column(name = "serial") val serial: Long - ) { + ) : Serializable { fun toNodeInfo(): NodeInfo { return NodeInfo( this.addresses.map { it.toHostAndPort() }, @@ -72,7 +73,7 @@ object NodeInfoSchemaV1 : MappedSchema( var id: Int, val host: String? = null, val port: Int? = null - ) { + ) : Serializable { companion object { fun fromHostAndPort(hostAndPort: NetworkHostAndPort) = DBHostAndPort( 0, hostAndPort.host, hostAndPort.port @@ -106,7 +107,7 @@ object NodeInfoSchemaV1 : MappedSchema( @ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = arrayOf(CascadeType.ALL)) // ManyToMany because of distributed services. private val persistentNodeInfos: Set = emptySet() - ) { + ) : Serializable { constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false) : this(partyAndCert.name.toString(), partyAndCert.party.owningKey.toStringShort(), diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index ca12c23390..df28f86674 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -31,6 +31,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger +import java.io.Serializable import java.time.Duration import java.time.Instant import java.util.* @@ -155,7 +156,7 @@ class NodeSchedulerService(private val clock: CordaClock, @Column(name = "scheduled_at", nullable = false) var scheduledAt: Instant = Instant.now() - ) + ) : Serializable private class InnerState { var scheduledStatesQueue: PriorityQueue = PriorityQueue({ a, b -> a.scheduledAt.compareTo(b.scheduledAt) }) diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 307e034fda..a76b1f39a7 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -17,6 +17,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY +import java.io.Serializable import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -75,7 +76,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, @Lob @Column(name = "identity_value") var identity: ByteArray = EMPTY_BYTE_ARRAY - ) + ) : Serializable @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}named_identities") @@ -86,7 +87,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE) var publicKeyHash: String = "" - ) + ) : Serializable override val caCertStore: CertStore override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index f31af4f276..4609add1a3 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -10,6 +10,7 @@ import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.bouncycastle.operator.ContentSigner +import java.io.Serializable import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey @@ -42,7 +43,7 @@ class PersistentKeyManagementService(val identityService: IdentityService, @Lob @Column(name = "private_key") var privateKey: ByteArray = EMPTY_BYTE_ARRAY - ) { + ) : Serializable { constructor(publicKey: PublicKey, privateKey: PrivateKey) : this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded) } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 16a1a34436..3fe6a368e4 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -46,6 +46,7 @@ import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Observable import rx.Subscription import rx.subjects.PublishSubject +import java.io.Serializable import java.security.PublicKey import java.time.Instant import java.util.* @@ -189,7 +190,7 @@ class P2PMessagingClient(private val config: NodeConfiguration, @Column(name = "insertion_time") var insertionTime: Instant = Instant.now() - ) + ) : Serializable @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}message_retry") @@ -204,7 +205,7 @@ class P2PMessagingClient(private val config: NodeConfiguration, @Lob @Column var recipients: ByteArray = EMPTY_BYTE_ARRAY - ) + ) : Serializable fun start() { state.locked { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt index 6507b86c47..f8140b4b50 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt @@ -6,6 +6,7 @@ import net.corda.node.services.api.CheckpointStorage import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY +import java.io.Serializable import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -26,7 +27,7 @@ class DBCheckpointStorage : CheckpointStorage { @Lob @Column(name = "checkpoint_value") var checkpoint: ByteArray = EMPTY_BYTE_ARRAY - ) + ) : Serializable override fun addCheckpoint(checkpoint: Checkpoint) { currentDBSession().save(DBCheckpoint().apply { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt index e81c6767f8..9e9137cf03 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt @@ -11,6 +11,7 @@ import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction import rx.subjects.PublishSubject +import java.io.Serializable import java.util.* import javax.annotation.concurrent.ThreadSafe import javax.persistence.* @@ -33,7 +34,7 @@ class DBTransactionMappingStorage : StateMachineRecordedTransactionMappingStorag @Column(name = "state_machine_run_id", length = 36) var stateMachineRunId: String = "" - ) + ) : Serializable private companion object { fun createMap(): AppendOnlyPersistentMap { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index 9661de7792..e2f298a7e1 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -16,6 +16,7 @@ import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Observable import rx.subjects.PublishSubject +import java.io.Serializable import java.util.* import javax.persistence.* @@ -36,7 +37,7 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S @Lob @Column(name = "transaction_value") var transaction: ByteArray = EMPTY_BYTE_ARRAY - ) + ) : Serializable private companion object { fun createTransactionsMap(maxSizeInBytes: Long) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt index afa84c9f2a..28a6aaab95 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt @@ -9,6 +9,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.slf4j.Logger import rx.subjects.PublishSubject +import java.io.Serializable import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -34,7 +35,7 @@ class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, persistenc @Column(name = "property_value") var value: String? = "" - ) + ) : Serializable } private class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private val persistence: CordaPersistence, logger: Logger) : FlowsDrainingModeOperations { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index f35fe71c10..f0ff1686fc 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -18,6 +18,7 @@ import net.corda.core.utilities.contextLogger import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession +import java.io.Serializable import java.time.Clock import java.time.Instant import java.util.* @@ -34,7 +35,7 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl @Column(name = "consuming_transaction_id") val consumingTxHash: String - ) + ) : Serializable @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_request_log") @@ -56,7 +57,7 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl @Column(name = "request_timestamp") var requestDate: Instant - ) + ) : Serializable @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_states") diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 22835d8666..f339373b51 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -32,6 +32,7 @@ import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX +import java.io.Serializable import java.nio.file.Path import java.time.Clock import java.util.concurrent.CompletableFuture @@ -94,7 +95,7 @@ class RaftUniquenessProvider( var value: String = "", @Column(name = "raft_log_index") var index: Long = 0 - ) + ) : Serializable /** Directory storing the Raft log and state machine snapshots */ private val storagePath: Path = transportConfiguration.baseDirectory diff --git a/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt b/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt index 162d8d945b..68f0854770 100644 --- a/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt @@ -6,6 +6,7 @@ import net.corda.core.node.services.ContractUpgradeService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.node.utilities.PersistentMap +import java.io.Serializable import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -23,7 +24,7 @@ class ContractUpgradeServiceImpl : ContractUpgradeService, SingletonSerializeAsT /** refers to the UpgradedContract class name*/ @Column(name = "contract_class_name") var upgradedContractClassName: String = "" - ) + ) : Serializable private companion object { fun createContractUpgradesMap(): PersistentMap { diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index a14cd60e5c..2734ed17b0 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -20,6 +20,7 @@ import org.hibernate.annotations.Cascade import org.hibernate.annotations.CascadeType import org.junit.Ignore import org.junit.Test +import java.io.Serializable import javax.persistence.* import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -141,7 +142,7 @@ object TestSchema : MappedSchema(SchemaFamily::class.java, 1, setOf(Parent::clas @Suppress("unused") @Entity @Table(name = "Children") - class Child { + class Child : Serializable { @Id @GeneratedValue @Column(name = "child_id", unique = true, nullable = false) From 6c9a39ae4405afb3987dd0963052420c9a978dc5 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 28 Mar 2018 09:40:21 +0100 Subject: [PATCH 2/3] DOCS: Serialization roundtrip removes mutability explanation (#2888) --- docs/source/serialization.rst | 79 +++++++++++++++++++ .../serialization/amqp/RoundTripTests.kt | 60 ++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt diff --git a/docs/source/serialization.rst b/docs/source/serialization.rst index de984b9b18..5a26d81cec 100644 --- a/docs/source/serialization.rst +++ b/docs/source/serialization.rst @@ -442,6 +442,85 @@ associates it with the actual member variable. fun getStatesToConsume() = states } +Mutable Containers +`````````````````` + +Because Java fundamentally provides no mechanism by which the mutability of a class can be determined this presents a +problem for the serialization framework. When reconstituting objects with container properties (lists, maps, etc) we +must chose whether to create mutable or immutable objects. Given the restrictions, we have decided it is better to +preserve the immutability of immutable objects rather than force mutability on presumed immutable objects. + +.. note:: Whilst we could potentially infer mutability empirically, doing so exhaustively is impossible as it's a design + decision rather than something intrinsic to the JVM. At present, we defer to simply making things immutable on reconstruction + with the following workarounds provided for those who use them. In future, this may change, but for now use the following + examples as a guide. + +For example, consider the following: + +.. sourcecode:: kotlin + + data class C(val l : MutableList) + + val bytes = C(mutableListOf ("a", "b", "c")).serialize() + val newC = bytes.deserialize() + + newC.l.add("d") + +The call to ``newC.l.add`` will throw an ``UnsupportedOperationException``. + +There are several workarounds that can be used to preserve mutability on reconstituted objects. Firstly, if the class +isn't a Kotlin data class and thus isn't restricted by having to have a primary constructor. + +.. sourcecode:: kotlin + + class C { + val l : MutableList + + @Suppress("Unused") + constructor (l : MutableList) { + this.l = l.toMutableList() + } + } + + val bytes = C(mutableListOf ("a", "b", "c")).serialize() + val newC = bytes.deserialize() + + // This time this call will succeed + newC.l.add("d") + +Secondly, if the class is a Kotlin data class, a secondary constructor can be used. + +.. sourcecode:: kotlin + + data class C (val l : MutableList){ + @ConstructorForDeserialization + @Suppress("Unused") + constructor (l : Collection) : this (l.toMutableList()) + } + + val bytes = C(mutableListOf ("a", "b", "c")).serialize() + val newC = bytes.deserialize() + + // This will also work + newC.l.add("d") + +Thirdly, to preserve immutability of objects (a recommend design principle - Copy on Write semantics) then mutating the +contents of the class can be done by creating a new copy of the data class with the altered list passed (in this example) +passed in as the Constructor parameter. + +.. sourcecode:: kotlin + + data class C(val l : List) + + val bytes = C(listOf ("a", "b", "c")).serialize() + val newC = bytes.deserialize() + + val newC2 = newC.copy (l = (newC.l + "d")) + +.. note:: If mutability isn't an issue at all then in the case of data classes a single constructor can + be used by making the property var instead of val and in the ``init`` block reassigning the property + to a mutable instance + Enums ````` diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt new file mode 100644 index 0000000000..f42823a862 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/RoundTripTests.kt @@ -0,0 +1,60 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import net.corda.core.serialization.ConstructorForDeserialization +import org.assertj.core.api.Assertions +import org.junit.Test + +class RoundTripTests { + @Test + fun mutableBecomesImmutable() { + data class C(val l : MutableList) + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(C(mutableListOf ("a", "b", "c"))) + val newC = DeserializationInput(factory).deserialize(bytes) + + Assertions.assertThatThrownBy { + newC.l.add("d") + }.isInstanceOf(UnsupportedOperationException::class.java) + } + + @Test + fun mutableStillMutable() { + class C { + val l : MutableList + + @Suppress("Unused") + constructor (l : MutableList) { + this.l = l.toMutableList() + } + } + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(C(mutableListOf ("a", "b", "c"))) + val newC = DeserializationInput(factory).deserialize(bytes) + + newC.l.add("d") + } + + @Test + fun mutableStillMutable2() { + data class C (val l : MutableList){ + @ConstructorForDeserialization + @Suppress("Unused") + constructor (l : Collection) : this (l.toMutableList()) + } + + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(C(mutableListOf ("a", "b", "c"))) + val newC = DeserializationInput(factory).deserialize(bytes) + + newC.l.add("d") + } + + @Test + fun mutableBecomesImmutable4() { + data class C(val l : List) + val factory = testDefaultFactoryNoEvolution() + val bytes = SerializationOutput(factory).serialize(C(listOf ("a", "b", "c"))) + val newC = DeserializationInput(factory).deserialize(bytes) + val newC2 = newC.copy (l = (newC.l + "d")) + } +} \ No newline at end of file From b93f12d4138e81a69980a911e1c126a92d0782fe Mon Sep 17 00:00:00 2001 From: sollecitom Date: Wed, 28 Mar 2018 10:58:41 +0100 Subject: [PATCH 3/3] Finished porting change to make JPA entities non-final and serializable. --- .../persistence/entity/CertificateRevocationListEntity.kt | 3 ++- .../entity/CertificateRevocationRequestEntity.kt | 3 ++- .../persistence/entity/CertificateSigningRequestEntity.kt | 7 ++++--- .../common/persistence/entity/NetworkMapEntity.kt | 3 ++- .../common/persistence/entity/NetworkParametersEntity.kt | 8 +++++--- .../common/persistence/entity/NodeInfoEntity.kt | 5 +++-- .../common/persistence/entity/ParametersUpdateEntity.kt | 3 ++- .../net/corda/node/services/persistence/RunOnceService.kt | 3 ++- .../corda/node/services/schema/NodeSchemaServiceTest.kt | 2 +- 9 files changed, 23 insertions(+), 14 deletions(-) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt index ef84d3fcf7..7a184b3fdc 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt @@ -1,6 +1,7 @@ package com.r3.corda.networkmanage.common.persistence.entity import com.r3.corda.networkmanage.common.persistence.CrlIssuer +import java.io.Serializable import java.security.cert.X509CRL import java.time.Instant import javax.persistence.* @@ -26,4 +27,4 @@ class CertificateRevocationListEntity( @Column(name = "modified_at", nullable = false) val modifiedAt: Instant = Instant.now() -) \ No newline at end of file +) : Serializable \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationRequestEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationRequestEntity.kt index c28d10c767..410bde5a42 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationRequestEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationRequestEntity.kt @@ -3,6 +3,7 @@ package com.r3.corda.networkmanage.common.persistence.entity import com.r3.corda.networkmanage.common.persistence.RequestStatus import net.corda.core.identity.CordaX500Name import org.hibernate.envers.Audited +import java.io.Serializable import java.math.BigInteger import java.security.cert.CRLReason import java.time.Instant @@ -57,4 +58,4 @@ data class CertificateRevocationRequestEntity( @Audited @Column(name = "remark", length = 256) val remark: String? = null -) \ No newline at end of file +) : Serializable \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateSigningRequestEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateSigningRequestEntity.kt index 7ba09df012..96661f4594 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateSigningRequestEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateSigningRequestEntity.kt @@ -19,6 +19,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.nodeapi.internal.crypto.x509Certificates import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.hibernate.envers.Audited +import java.io.Serializable import java.math.BigInteger import java.security.cert.CertPath import java.time.Instant @@ -69,7 +70,7 @@ data class CertificateSigningRequestEntity( @ManyToOne @JoinColumn(name = "private_network", foreignKey = ForeignKey(name = "FK__CSR__PN")) val privateNetwork: PrivateNetworkEntity? = null -) { +) : Serializable { fun toCertificateSigningRequest(): CertificateSigningRequest { return CertificateSigningRequest( requestId = requestId, @@ -108,7 +109,7 @@ data class CertificateDataEntity( @Column(name = "cert_serial_number", unique = true, nullable = false, columnDefinition = "NUMERIC(28)") val certificateSerialNumber: BigInteger -) { +) : Serializable { fun toCertificateData(): CertificateData = CertificateData(certificateStatus, certPath) val legalName: CordaX500Name get() { @@ -125,4 +126,4 @@ data class PrivateNetworkEntity( @Column(name = "name", length = 255, nullable = false) val networkName: String -) \ No newline at end of file +) : Serializable \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt index c080d33e69..86dd4b73de 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt @@ -14,6 +14,7 @@ import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap +import java.io.Serializable import java.security.cert.X509Certificate import javax.persistence.* @@ -41,7 +42,7 @@ class NetworkMapEntity( @ManyToOne(optional = false, fetch = FetchType.EAGER) @JoinColumn(name = "network_parameters", nullable = false) val networkParameters: NetworkParametersEntity -) { +) : Serializable { fun toSignedNetworkMap(): SignedNetworkMap { return SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(certificate, signature)) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt index 4fe42149c1..3c8424e95f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt @@ -14,6 +14,7 @@ import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NetworkParameters import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.network.SignedNetworkParameters +import java.io.Serializable import java.security.cert.X509Certificate import java.time.Instant import javax.persistence.* @@ -43,12 +44,13 @@ class NetworkParametersEntity( @Column(name = "cert") @Convert(converter = X509CertificateConverter::class) val certificate: X509Certificate? -) { +) : Serializable { val isSigned: Boolean get() = certificate != null && signature != null fun toSignedNetworkParameters(): SignedNetworkParameters { - if (certificate == null || signature == null) throw IllegalStateException("Network parameters entity is not signed: $hash") - return SignedNetworkParameters(networkParameters.serialize(), DigitalSignatureWithCert(certificate, signature)) + val cert = certificate ?: throw IllegalStateException("Network parameters entity is not signed: $hash") + val sign = signature ?: throw IllegalStateException("Network parameters entity is not signed: $hash") + return SignedNetworkParameters(networkParameters.serialize(), DigitalSignatureWithCert(cert, sign)) } fun copy(parametersHash: String = this.hash, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt index ebea041651..b28f9eda00 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt @@ -11,6 +11,7 @@ package com.r3.corda.networkmanage.common.persistence.entity import net.corda.nodeapi.internal.SignedNodeInfo +import java.io.Serializable import java.time.Instant import javax.persistence.* @@ -40,7 +41,7 @@ data class NodeInfoEntity( @Column(name = "published_at", nullable = false) val publishedAt: Instant = Instant.now(), - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "accepted_params_update") val acceptedParametersUpdate: ParametersUpdateEntity? -) +) : Serializable diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt index 97e7c9385d..efdefe897b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt @@ -2,6 +2,7 @@ package com.r3.corda.networkmanage.common.persistence.entity import net.corda.core.crypto.SecureHash import net.corda.nodeapi.internal.network.ParametersUpdate +import java.io.Serializable import java.time.Instant import javax.persistence.* @@ -26,7 +27,7 @@ data class ParametersUpdateEntity( @Column(name = "status", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)") @Enumerated(EnumType.STRING) val status: UpdateStatus = UpdateStatus.NEW -) { +) : Serializable { fun toParametersUpdate(): ParametersUpdate { return ParametersUpdate(SecureHash.parse(networkParameters.hash), description, updateDeadline) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/RunOnceService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/RunOnceService.kt index 26d9b9e3fe..550d3701a0 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/RunOnceService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/RunOnceService.kt @@ -16,6 +16,7 @@ import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.hibernate.Session +import java.io.Serializable import java.time.Duration import java.time.LocalDateTime import java.time.temporal.ChronoField @@ -57,7 +58,7 @@ class RunOnceService(private val database: CordaPersistence, private val machine @Entity @Table(name = TABLE) - class MutualExclusion(machineNameInit: String, pidInit: String, timeStampInit: LocalDateTime, versionInit: Long = 0) { + class MutualExclusion(machineNameInit: String, pidInit: String, timeStampInit: LocalDateTime, versionInit: Long = 0) : Serializable { @Column(name = ID, insertable = false, updatable = false) @Id val id: Char = 'X' diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index a7d3433f57..eea4fe8edc 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -155,7 +155,7 @@ object TestSchema : MappedSchema(SchemaFamily::class.java, 1, setOf(Parent::clas @Suppress("unused") @Entity @Table(name = "children") - class Child { + class Child : Serializable { @Id @GeneratedValue @Column(name = "child_id", unique = true, nullable = false)