From 1535a5f60128e9fe26cb04f02f3ca2f5ccfc5888 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 9 May 2018 13:52:58 +0100 Subject: [PATCH 1/3] CORDA-1315 small doc correction (#3079) * CORDA-1315 small doc correction * CORDA-1315 address code review changes --- docs/source/upgrading-cordapps.rst | 136 +++++++++++++++-------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/docs/source/upgrading-cordapps.rst b/docs/source/upgrading-cordapps.rst index 8d0a0e3865..4d5aa53aea 100644 --- a/docs/source/upgrading-cordapps.rst +++ b/docs/source/upgrading-cordapps.rst @@ -475,47 +475,48 @@ existing object relational mapper. For example, we can update: .. sourcecode:: java public class ObligationSchemaV1 extends MappedSchema { - public IOUSchemaV1() { + public ObligationSchemaV1() { super(Obligation.class, 1, ImmutableList.of(ObligationEntity.class)); } + } - @Entity - @Table(name = "obligations") - public static class ObligationEntity extends PersistentState { - @Column(name = "currency") private final String currency; - @Column(name = "amount") private final Long amount; - @Column(name = "lender") @Lob private final Byte[] lender; - @Column(name = "borrower") @Lob private final Byte[] borrower; - @Column(name = "linear_id") private final UUID linearId; + @Entity + @Table(name = "obligations") + public class ObligationEntity extends PersistentState { + @Column(name = "currency") private String currency; + @Column(name = "amount") private Long amount; + @Column(name = "lender") @Lob private byte[] lender; + @Column(name = "borrower") @Lob private byte[] borrower; + @Column(name = "linear_id") private UUID linearId; + protected ObligationEntity(){} - public ObligationEntity(String currency, Long amount, Byte[] lender, Byte[] borrower, UUID linearId) { - this.currency = currency; - this.amount = amount; - this.lender = lender; - this.borrower = borrower; - this.linearId = linearId; - } + public ObligationEntity(String currency, Long amount, byte[] lender, byte[] borrower, UUID linearId) { + this.currency = currency; + this.amount = amount; + this.lender = lender; + this.borrower = borrower; + this.linearId = linearId; + } - public String getCurrency() { - return currency; - } + public String getCurrency() { + return currency; + } - public Long getAmount() { - return amount; - } + public Long getAmount() { + return amount; + } - public ByteArray getLender() { - return lender; - } + public byte[] getLender() { + return lender; + } - public ByteArray getBorrower() { - return borrower; - } + public byte[] getBorrower() { + return borrower; + } - public UUID getId() { - return linearId; - } + public UUID getLinearId() { + return linearId; } } @@ -540,53 +541,54 @@ To: .. sourcecode:: java public class ObligationSchemaV1 extends MappedSchema { - public IOUSchemaV1() { + public ObligationSchemaV1() { super(Obligation.class, 1, ImmutableList.of(ObligationEntity.class)); } + } - @Entity - @Table(name = "obligations") - public static class ObligationEntity extends PersistentState { - @Column(name = "currency") private final String currency; - @Column(name = "amount") private final Long amount; - @Column(name = "lender") @Lob private final Byte[] lender; - @Column(name = "borrower") @Lob private final Byte[] borrower; - @Column(name = "linear_id") private final UUID linearId; - @Column(name = "defaulted") private final Boolean defaulted; // NEW COLUMN! + @Entity + @Table(name = "obligations") + public class ObligationEntity extends PersistentState { + @Column(name = "currency") private String currency; + @Column(name = "amount") private Long amount; + @Column(name = "lender") @Lob private byte[] lender; + @Column(name = "borrower") @Lob private byte[] borrower; + @Column(name = "linear_id") private UUID linearId; + @Column(name = "defaulted") private Boolean defaulted; // NEW COLUMN! + protected ObligationEntity(){} - public ObligationEntity(String currency, Long amount, Byte[] lender, Byte[] borrower, UUID linearId, Boolean defaulted) { - this.currency = currency; - this.amount = amount; - this.lender = lender; - this.borrower = borrower; - this.linearId = linearId; - this.defaulted = defaulted; - } + public ObligationEntity(String currency, Long amount, byte[] lender, byte[] borrower, UUID linearId, Boolean defaulted) { + this.currency = currency; + this.amount = amount; + this.lender = lender; + this.borrower = borrower; + this.linearId = linearId; + this.defaulted = defaulted; + } - public String getCurrency() { - return currency; - } + public String getCurrency() { + return currency; + } - public Long getAmount() { - return amount; - } + public Long getAmount() { + return amount; + } - public ByteArray getLender() { - return lender; - } + public byte[] getLender() { + return lender; + } - public ByteArray getBorrower() { - return borrower; - } + public byte[] getBorrower() { + return borrower; + } - public UUID getId() { - return linearId; - } + public UUID getLinearId() { + return linearId; + } - public Boolean isDefaulted() { - return defaulted; - } + public Boolean isDefaulted() { + return defaulted; } } From bb95156262edc9a57ac2578435d5ef6ccdf2c63a Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Wed, 9 May 2018 15:45:31 +0100 Subject: [PATCH 2/3] ENT-1447 Database transaction optional ability to retry on nested SQLException. (#3046) Database transaction can be set to retry failure due to any exception with a cause or a nested cause of SQLException type. Rationale: By the default transaction is retried only for SQLException. It may happen that SQL Exception is wrapped by Hibernate exception, allow to retry such cases if requested e.g. database.transaction(recoverableFailureTolerance = 3, recoverAnyNestedSQLException = true) { .... } --- .../internal/persistence/CordaPersistence.kt | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 0c8c5724ed..28939f1abf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -116,10 +116,8 @@ class CordaPersistence( * @param isolationLevel isolation level for the transaction. * @param statement to be executed in the scope of this transaction. */ - fun transaction(isolationLevel: TransactionIsolationLevel, statement: DatabaseTransaction.() -> T): T { - _contextDatabase.set(this) - return transaction(isolationLevel, 2, statement) - } + fun transaction(isolationLevel: TransactionIsolationLevel, statement: DatabaseTransaction.() -> T): T = + transaction(isolationLevel, 2, false, statement) /** * Executes given statement in the scope of transaction with the transaction level specified at the creation time. @@ -127,16 +125,26 @@ class CordaPersistence( */ fun transaction(statement: DatabaseTransaction.() -> T): T = transaction(defaultIsolationLevel, statement) - private fun transaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, statement: DatabaseTransaction.() -> T): T { + /** + * Executes given statement in the scope of transaction, with the given isolation level. + * @param isolationLevel isolation level for the transaction. + * @param recoverableFailureTolerance number of transaction commit retries for SQL while SQL exception is encountered. + * @param recoverAnyNestedSQLException retry transaction on any SQL Exception wrapped as a cause of [Throwable]. + * @param statement to be executed in the scope of this transaction. + */ + fun transaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, + recoverAnyNestedSQLException: Boolean, statement: DatabaseTransaction.() -> T): T { + _contextDatabase.set(this) val outer = contextTransactionOrNull return if (outer != null) { outer.statement() } else { - inTopLevelTransaction(isolationLevel, recoverableFailureTolerance, statement) + inTopLevelTransaction(isolationLevel, recoverableFailureTolerance, recoverAnyNestedSQLException, statement) } } - private fun inTopLevelTransaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, statement: DatabaseTransaction.() -> T): T { + private fun inTopLevelTransaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, + recoverAnyNestedSQLException: Boolean, statement: DatabaseTransaction.() -> T): T { var recoverableFailureCount = 0 fun quietly(task: () -> T) = try { task() @@ -149,13 +157,14 @@ class CordaPersistence( val answer = transaction.statement() transaction.commit() return answer - } catch (e: SQLException) { - quietly(transaction::rollback) - if (++recoverableFailureCount > recoverableFailureTolerance) throw e - log.warn("Caught failure, will retry:", e) } catch (e: Throwable) { quietly(transaction::rollback) - throw e + if (e is SQLException || (recoverAnyNestedSQLException && e.hasSQLExceptionCause())) { + if (++recoverableFailureCount > recoverableFailureTolerance) throw e + log.warn("Caught failure, will retry $recoverableFailureCount/$recoverableFailureTolerance:", e) + } else { + throw e + } } finally { quietly(transaction::close) } @@ -246,3 +255,12 @@ fun rx.Observable.wrapWithDatabaseTransaction(db: CordaPersistence? } } } + +/** Check if any nested cause is of [SQLException] type. */ +private fun Throwable.hasSQLExceptionCause(): Boolean = + if (cause == null) + false + else if (cause is SQLException) + true + else + cause?.hasSQLExceptionCause() ?: false \ No newline at end of file From fe88e9907cac92d8884d0f634a2fe44608b03ea4 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Wed, 9 May 2018 16:36:17 +0100 Subject: [PATCH 3/3] ENT-1463: Replace getStackTraceAsString() with Throwable.initCause(). (#3101) --- .../main/kotlin/net/corda/core/internal/InternalUtils.kt | 1 - .../internal/serialization/amqp/DeserializationInput.kt | 7 +++---- .../serialization/amqp/SerializationOutputTests.kt | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 8590cb5a64..467b2aa88f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -58,7 +58,6 @@ import kotlin.reflect.KClass import kotlin.reflect.full.createInstance val Throwable.rootCause: Throwable get() = cause?.rootCause ?: this -fun Throwable.getStackTraceAsString() = StringWriter().also { printStackTrace(PrintWriter(it)) }.toString() infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index 592d21f638..3b1686fbbb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.serialization.amqp import com.esotericsoftware.kryo.io.ByteBufferInputStream import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.getStackTraceAsString import net.corda.core.serialization.EncodingWhitelist import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializedBytes @@ -95,7 +94,7 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto @Throws(NotSerializableException::class) - fun getEnvelope(byteSequence: ByteSequence) = Companion.getEnvelope(byteSequence, encodingWhitelist) + fun getEnvelope(byteSequence: ByteSequence) = getEnvelope(byteSequence, encodingWhitelist) @Throws(NotSerializableException::class) inline fun deserialize(bytes: SerializedBytes, context: SerializationContext): T = @@ -109,7 +108,7 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto } catch (nse: NotSerializableException) { throw nse } catch (t: Throwable) { - throw NotSerializableException("Unexpected throwable: ${t.message} ${t.getStackTraceAsString()}") + throw NotSerializableException("Unexpected throwable: ${t.message}").apply { initCause(t) } } finally { objectHistory.clear() } @@ -163,7 +162,7 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) { throw NotSerializableException( "Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " + - "@ ${objectIndex}") + "@ $objectIndex") } objectRetrieved } else { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 290b5e89f6..63f3634185 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -487,7 +487,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi copy[valueIndex] = 0x00 assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy { des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java, testSerializationContext) - }.withMessageContaining("Zero not allowed") + }.withStackTraceContaining("Zero not allowed") } @Test