Simple Attachment Storage implementation using Requery/H2 database

This commit is contained in:
Karel Hajek
2017-02-15 18:32:23 +00:00
committed by Mike Hearn
parent 0600bfa061
commit 1e78d6a3a7
16 changed files with 343 additions and 117 deletions

View File

@ -204,7 +204,7 @@ inline fun elapsedTime(block: () -> Unit): Duration {
val start = System.nanoTime()
block()
val end = System.nanoTime()
return Duration.ofNanos(end-start)
return Duration.ofNanos(end - start)
}
// TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError
@ -280,13 +280,16 @@ class TransientProperty<out T>(private val initializer: () -> T) {
/**
* Given a path to a zip file, extracts it to the given directory.
*/
fun extractZipFile(zipFile: Path, toDirectory: Path) {
val normalisedDirectory = toDirectory.normalize().createDirectories()
fun extractZipFile(zipFile: Path, toDirectory: Path) = extractZipFile(Files.newInputStream(zipFile), toDirectory)
zipFile.read {
val zip = ZipInputStream(BufferedInputStream(it))
/**
* Given a zip file input stream, extracts it to the given directory.
*/
fun extractZipFile(inputStream: InputStream, toDirectory: Path) {
val normalisedDirectory = toDirectory.normalize().createDirectories()
ZipInputStream(BufferedInputStream(inputStream)).use {
while (true) {
val e = zip.nextEntry ?: break
val e = it.nextEntry ?: break
val outPath = (normalisedDirectory / e.name).normalize()
// Security checks: we should reject a zip that contains tricksy paths that try to escape toDirectory.
@ -297,9 +300,9 @@ fun extractZipFile(zipFile: Path, toDirectory: Path) {
continue
}
outPath.write { out ->
ByteStreams.copy(zip, out)
ByteStreams.copy(it, out)
}
zip.closeEntry()
it.closeEntry()
}
}
}
@ -394,13 +397,16 @@ private class ObservableToFuture<T>(observable: Observable<T>) : AbstractFuture<
override fun onNext(value: T) {
set(value)
}
override fun onError(e: Throwable) {
setException(e)
}
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
subscription.unsubscribe()
return super.cancel(mayInterruptIfRunning)
}
override fun onCompleted() {}
}

View File

@ -3,11 +3,20 @@ package net.corda.core.node.services
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import java.io.InputStream
import java.nio.file.Path
/**
* An attachment store records potentially large binary objects, identified by their hash.
*/
interface AttachmentStorage {
/**
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
* human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
* will not have any effect).
*/
var automaticallyExtractAttachments : Boolean
var storePath : Path
/**
* Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open
* a stream for the data, which will be a zip/jar file.

View File

@ -0,0 +1,28 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import java.sql.Blob
import javax.sql.rowset.serial.SerialBlob
/**
* Converts from a [ByteArray] to a [Blob].
*/
class BlobConverter : Converter<ByteArray, Blob> {
override fun getMappedType(): Class<ByteArray> = ByteArray::class.java
override fun getPersistedType(): Class<Blob> = Blob::class.java
/**
* creates BLOB(INT.MAX) = 2 GB
*/
override fun getPersistedSize(): Int? = null
override fun convertToPersisted(value: ByteArray?): Blob? {
return value?.let { SerialBlob(value) }
}
override fun convertToMapped(type: Class<out ByteArray>?, value: Blob?): ByteArray? {
return value?.getBytes(1, value.length().toInt())
}
}

View File

@ -0,0 +1,28 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import net.corda.core.crypto.SecureHash
/**
* Convert from a [SecureHash] to a [String]
*/
class SecureHashConverter : Converter<SecureHash, String> {
override fun getMappedType(): Class<SecureHash> = SecureHash::class.java
override fun getPersistedType(): Class<String> = String::class.java
/**
* SecureHash consists of 32 bytes which need VARCHAR(64) in hex
* TODO: think about other hash widths
*/
override fun getPersistedSize(): Int? = 64
override fun convertToPersisted(value: SecureHash?): String? {
return value?.toString()
}
override fun convertToMapped(type: Class<out SecureHash>, value: String?): SecureHash? {
return value?.let { SecureHash.parse(value) }
}
}

View File

@ -134,13 +134,20 @@ class ResolveTransactionsFlowTest {
@Test
fun attachment() {
val id = a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
// TODO: this operation should not require an explicit transaction
val id = databaseTransaction(a.database) {
a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
}
val stx2 = makeTransactions(withAttachment = id).second
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
val future = b.services.startFlow(p).resultFuture
net.runNetwork()
future.getOrThrow()
assertNotNull(b.services.storageService.attachments.openAttachment(id))
// TODO: this operation should not require an explicit transaction
databaseTransaction(b.database) {
assertNotNull(b.services.storageService.attachments.openAttachment(id))
}
}
// DOCSTART 2