Tighten up transaction creation code to flag situations where calling code SHOULD BE within a demarcated transaction boundary. (#869)

Now fail fast with an appropriate message indicating course of action to be taken.
This commit is contained in:
josecoll 2017-06-20 20:33:31 +01:00 committed by GitHub
parent d54f66ccb0
commit 8aa325d9f7
3 changed files with 56 additions and 48 deletions

View File

@ -131,7 +131,7 @@ class KotlinConfigurationTransactionWrapper(private val model: EntityModel,
override fun getConnection(): Connection { override fun getConnection(): Connection {
val tx = TransactionManager.manager.currentOrNull() val tx = TransactionManager.manager.currentOrNull()
return CordaConnection( return CordaConnection(
tx?.connection ?: TransactionManager.manager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ).connection tx?.connection ?: throw IllegalStateException("Was expecting to find database transaction: must wrap calling code within a transaction.")
) )
} }
} }

View File

@ -48,7 +48,7 @@ class RequeryConfiguration(val properties: Properties, val useDefaultLogging: Bo
// TODO: remove once Requery supports QUERY WITH COMPOSITE_KEY IN // TODO: remove once Requery supports QUERY WITH COMPOSITE_KEY IN
fun jdbcSession(): Connection { fun jdbcSession(): Connection {
val ctx = TransactionManager.manager.currentOrNull() val ctx = TransactionManager.manager.currentOrNull()
return ctx?.connection ?: TransactionManager.manager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ).connection return ctx?.connection ?: throw IllegalStateException("Was expecting to find database transaction: must wrap calling code within a transaction.")
} }
} }

View File

@ -14,9 +14,9 @@ import net.corda.node.services.database.RequeryConfiguration
import net.corda.node.services.persistence.schemas.AttachmentEntity import net.corda.node.services.persistence.schemas.AttachmentEntity
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
import net.corda.node.utilities.transaction
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDataSourceProperties
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -55,7 +55,7 @@ class NodeAttachmentStorageTest {
@After @After
fun tearDown() { fun tearDown() {
TransactionManager.current().close() dataSource.close()
} }
@Test @Test
@ -63,76 +63,84 @@ class NodeAttachmentStorageTest {
val testJar = makeTestJar() val testJar = makeTestJar()
val expectedHash = testJar.readAll().sha256() val expectedHash = testJar.readAll().sha256()
val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry()) database.transaction {
val id = testJar.read { storage.importAttachment(it) } val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry())
assertEquals(expectedHash, id) val id = testJar.read { storage.importAttachment(it) }
assertEquals(expectedHash, id)
assertNull(storage.openAttachment(SecureHash.randomSHA256())) assertNull(storage.openAttachment(SecureHash.randomSHA256()))
val stream = storage.openAttachment(expectedHash)!!.openAsJAR() val stream = storage.openAttachment(expectedHash)!!.openAsJAR()
val e1 = stream.nextJarEntry!! val e1 = stream.nextJarEntry!!
assertEquals("test1.txt", e1.name) assertEquals("test1.txt", e1.name)
assertEquals(stream.readBytes().toString(Charset.defaultCharset()), "This is some useful content") assertEquals(stream.readBytes().toString(Charset.defaultCharset()), "This is some useful content")
val e2 = stream.nextJarEntry!! val e2 = stream.nextJarEntry!!
assertEquals("test2.txt", e2.name) assertEquals("test2.txt", e2.name)
assertEquals(stream.readBytes().toString(Charset.defaultCharset()), "Some more useful content") assertEquals(stream.readBytes().toString(Charset.defaultCharset()), "Some more useful content")
stream.close() stream.close()
storage.openAttachment(id)!!.openAsJAR().use { storage.openAttachment(id)!!.openAsJAR().use {
it.nextJarEntry it.nextJarEntry
it.readBytes() it.readBytes()
}
} }
} }
@Test @Test
fun `duplicates not allowed`() { fun `duplicates not allowed`() {
val testJar = makeTestJar() val testJar = makeTestJar()
val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry()) database.transaction {
testJar.read { val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry())
storage.importAttachment(it)
}
assertFailsWith<FileAlreadyExistsException> {
testJar.read { testJar.read {
storage.importAttachment(it) storage.importAttachment(it)
} }
assertFailsWith<FileAlreadyExistsException> {
testJar.read {
storage.importAttachment(it)
}
}
} }
} }
@Test @Test
fun `corrupt entry throws exception`() { fun `corrupt entry throws exception`() {
val testJar = makeTestJar() val testJar = makeTestJar()
val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry()) database.transaction {
val id = testJar.read { storage.importAttachment(it) } val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry())
val id = testJar.read { storage.importAttachment(it) }
// Corrupt the file in the store. // Corrupt the file in the store.
val bytes = testJar.readAll() val bytes = testJar.readAll()
val corruptBytes = "arggghhhh".toByteArray() val corruptBytes = "arggghhhh".toByteArray()
System.arraycopy(corruptBytes, 0, bytes, 0, corruptBytes.size) System.arraycopy(corruptBytes, 0, bytes, 0, corruptBytes.size)
val corruptAttachment = AttachmentEntity() val corruptAttachment = AttachmentEntity()
corruptAttachment.attId = id corruptAttachment.attId = id
corruptAttachment.content = bytes corruptAttachment.content = bytes
storage.session.update(corruptAttachment) storage.session.update(corruptAttachment)
val e = assertFailsWith<NodeAttachmentService.HashMismatchException> { val e = assertFailsWith<NodeAttachmentService.HashMismatchException> {
storage.openAttachment(id)!!.open().use { it.readBytes() } storage.openAttachment(id)!!.open().use { it.readBytes() }
} }
assertEquals(e.expected, id) assertEquals(e.expected, id)
// But if we skip around and read a single entry, no exception is thrown. // But if we skip around and read a single entry, no exception is thrown.
storage.openAttachment(id)!!.openAsJAR().use { storage.openAttachment(id)!!.openAsJAR().use {
it.nextJarEntry it.nextJarEntry
it.readBytes() it.readBytes()
}
} }
} }
@Test @Test
fun `non jar rejected`() { fun `non jar rejected`() {
val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry()) database.transaction {
val path = fs.getPath("notajar") val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry())
path.writeLines(listOf("Hey", "there!")) val path = fs.getPath("notajar")
path.read { path.writeLines(listOf("Hey", "there!"))
assertFailsWith<IllegalArgumentException>("either empty or not a JAR") { path.read {
storage.importAttachment(it) assertFailsWith<IllegalArgumentException>("either empty or not a JAR") {
storage.importAttachment(it)
}
} }
} }
} }