Custom ClassLoader created from collection of attachments

This commit is contained in:
sofusmortensen 2016-03-20 20:41:46 +01:00
parent 2a72f38076
commit 3675675277
8 changed files with 165 additions and 5 deletions

2
.gitignore vendored
View File

@ -13,7 +13,7 @@ tags
.gradle
/build/
/contracts/build
/sandbox/build
/standalone.test/build
/core/build
/docs/build/doctrees

View File

@ -1,4 +1,4 @@
rootProject.name = 'r3prototyping'
include 'contracts'
include 'core'
include 'sandbox'
include 'standalone.test'

View File

@ -0,0 +1,64 @@
package core.node
import core.Attachment
import java.io.Closeable
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.net.URLClassLoader
import java.util.*
import java.util.jar.JarEntry
class OverlappingAttachments : Exception()
/**
* A custom ClassLoader for creating contracts distributed as attachments and for contracts to
* access attachments.
*
*
*/
class ClassLoader private constructor(val tmpFiles: List<File> )
: URLClassLoader(tmpFiles.map { URL("file", "", it.toString()) }.toTypedArray()), Closeable {
override fun close() {
super.close()
for (file in tmpFiles) {
file.delete()
}
}
companion object {
fun create(streams: List<Attachment>) : ClassLoader {
validate(streams)
var tmpFiles = streams.map {
var filename = File.createTempFile("jar", "")
it.open().use {
str -> FileOutputStream(filename).use { str.copyTo(it) }
}
filename
}
return ClassLoader(tmpFiles)
}
private fun validate(streams: List<Attachment>) {
val set = HashSet<String>()
val jars = streams.map { it.openAsJAR() }
for (jar in jars) {
var entry: JarEntry = jar.nextJarEntry ?: continue
if (set.add(entry.name) == false) {
throw OverlappingAttachments()
}
}
}
}
}

View File

@ -92,7 +92,7 @@ class MockAttachmentStorage : AttachmentStorage {
override fun openAttachment(id: SecureHash): Attachment? {
val f = files[id] ?: return null
return object : Attachment {
override fun open(): JarInputStream = JarInputStream(ByteArrayInputStream(f))
override fun open(): InputStream = ByteArrayInputStream(f)
override val id: SecureHash = id
}
}
@ -104,6 +104,7 @@ class MockAttachmentStorage : AttachmentStorage {
val bytes = run {
val s = ByteArrayOutputStream()
jar.copyTo(s)
s.close()
s.toByteArray()
}
val sha256 = bytes.sha256()

View File

@ -46,7 +46,7 @@ class AttachmentTests {
val bs = ByteArrayOutputStream()
val js = JarOutputStream(bs)
js.putNextEntry(ZipEntry("file1.txt"))
js.writer().append("Some useful content")
js.writer().apply { append("Some useful content"); flush() }
js.closeEntry()
js.close()
return bs.toByteArray()

View File

@ -0,0 +1,94 @@
package core.node
import core.Contract
import core.MockAttachmentStorage
import core.crypto.SecureHash
import org.apache.commons.io.IOUtils
import org.junit.Ignore
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ClassLoaderTests {
fun fakeAttachment(filepath: String, content: String): ByteArray {
val bs = ByteArrayOutputStream()
val js = JarOutputStream(bs)
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
js.close()
return bs.toByteArray()
}
@Test
fun `test MockAttachmentStorage open as jar`() {
val storage = MockAttachmentStorage()
val key = storage.importAttachment( FileInputStream("contracts/build/libs/contracts.jar") )
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
assert( jar.nextEntry != null )
}
@Test
fun `test overlapping file exception`() {
var storage = MockAttachmentStorage()
var att0 = storage.importAttachment( FileInputStream("contracts/build/libs/contracts.jar") )
var att1 = storage.importAttachment( ByteArrayInputStream(fakeAttachment("file.txt", "some data")) )
var att2 = storage.importAttachment( ByteArrayInputStream(fakeAttachment("file.txt", "some other data")) )
assertFailsWith( OverlappingAttachments::class ) {
var cl = ClassLoader.create(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! })
}
}
@Test
fun `basic`() {
var storage = MockAttachmentStorage()
var att0 = storage.importAttachment( FileInputStream("contracts/build/libs/contracts.jar") )
var att1 = storage.importAttachment( ByteArrayInputStream(fakeAttachment("file1.txt", "some data")) )
var att2 = storage.importAttachment( ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")) )
ClassLoader.create( arrayOf( att0, att1, att2 ).map { storage.openAttachment(it)!! } ).use {
var txt = IOUtils.toString(it.getResourceAsStream("file1.txt"))
assertEquals( "some data", txt )
}
}
@Test
fun `loading class Cash`() {
var storage = MockAttachmentStorage()
var att0 = storage.importAttachment( FileInputStream("contracts/build/libs/contracts.jar") )
var att1 = storage.importAttachment( ByteArrayInputStream(fakeAttachment("file1.txt", "some data")) )
var att2 = storage.importAttachment( ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")) )
ClassLoader.create( arrayOf( att0, att1, att2 ).map { storage.openAttachment(it)!! } ).use {
var contractClass = Class.forName("contracts.Cash", true, it)
var contract = contractClass.newInstance() as Contract
assertEquals(SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html"), contract.legalContractReference)
}
}
@Ignore
@Test
fun `testing Kryo with ClassLoader`() {
assert(false) // todo
}
}

View File

@ -3,12 +3,13 @@ import core.crypto.SecureHash
import org.junit.Test
import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarInputStream
import kotlin.test.assertEquals
class LoaderTests {
@Test
fun loadContracts() {
fun `dynamically load Cash class from contracts jar`() {
var child = URLClassLoader(arrayOf(URL("file", "", "../contracts/build/libs/contracts.jar")))
var contractClass = Class.forName("contracts.Cash", true, child)