mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +00:00
Feature/corda 1947/add package ownership (#4097)
* Upgrade hibernate and fix tests CORDA-1947 Address code review changes CORDA-1947 Address code review changes (cherry picked from commitab98c03d1a
) * ENT-2506 Changes signers field type ENT-2506 Clean up some docs ENT-2506 Fix tests and api ENT-2506 Fix compilation error ENT-2506 Fix compilation error (cherry picked from commit32f279a243
) * CORDA-1947 added packageOwnership parameter CORDA-1947 add signers field to DbAttachment. Add check when importing attachments CORDA-1947 add signers field to DbAttachment. Add check when importing attachments CORDA-1947 add tests CORDA-1947 fix comment CORDA-1947 Fix test CORDA-1947 fix serialiser CORDA-1947 fix tests CORDA-1947 fix tests CORDA-1947 fix serialiser CORDA-1947 Address code review changes CORDA-1947 Address code review changes CORDA-1947 Revert test fixes CORDA-1947 address code review comments CORDA-1947 move verification logic to LedgerTransaction.verify CORDA-1947 fix test CORDA-1947 fix tests CORDA-1947 fix tests CORDA-1947 address code review comments CORDA-1947 address code review comments (cherry picked from commit86bc0d9606
) CORDA-1947 fix merge
This commit is contained in:
@ -170,7 +170,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments).tokenize()
|
||||
@Suppress("LeakingThis")
|
||||
val keyManagementService = makeKeyManagementService(identityService).tokenize()
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, transactionStorage)
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, transactionStorage).also {
|
||||
attachments.servicesForResolution = it
|
||||
}
|
||||
@Suppress("LeakingThis")
|
||||
val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize()
|
||||
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
|
||||
@ -1036,7 +1038,7 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig,
|
||||
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
||||
// either Hibernate can be convinced to stop warning, use the descriptor by default, or something else.
|
||||
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
|
||||
val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
|
||||
val attributeConverters = listOf(PublicKeyToTextConverter(), AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
|
||||
val jdbcUrl = hikariProperties.getProperty("dataSource.url", "")
|
||||
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, cacheFactory, attributeConverters)
|
||||
}
|
||||
|
@ -209,15 +209,17 @@ object DefaultKryoCustomizer {
|
||||
output.writeString(obj.contract)
|
||||
kryo.writeClassAndObject(output, obj.additionalContracts)
|
||||
output.writeString(obj.uploader)
|
||||
kryo.writeClassAndObject(output, obj.signers)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<ContractAttachment>): ContractAttachment {
|
||||
if (kryo.serializationContext() != null) {
|
||||
val attachmentHash = SecureHash.SHA256(input.readBytes(32))
|
||||
val contract = input.readString()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val additionalContracts = kryo.readClassAndObject(input) as Set<ContractClassName>
|
||||
val uploader = input.readString()
|
||||
val signers = kryo.readClassAndObject(input) as List<PublicKey>
|
||||
val context = kryo.serializationContext()!!
|
||||
val attachmentStorage = context.serviceHub.attachments
|
||||
|
||||
@ -229,14 +231,14 @@ object DefaultKryoCustomizer {
|
||||
override val id = attachmentHash
|
||||
}
|
||||
|
||||
return ContractAttachment(lazyAttachment, contract, additionalContracts, uploader)
|
||||
return ContractAttachment(lazyAttachment, contract, additionalContracts, uploader, signers)
|
||||
} else {
|
||||
val attachment = GeneratedAttachment(input.readBytesWithLength())
|
||||
val contract = input.readString()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val additionalContracts = kryo.readClassAndObject(input) as Set<ContractClassName>
|
||||
val uploader = input.readString()
|
||||
return ContractAttachment(attachment, contract, additionalContracts, uploader)
|
||||
val signers = kryo.readClassAndObject(input) as List<PublicKey>
|
||||
return ContractAttachment(attachment, contract, additionalContracts, uploader, signers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,16 @@ import com.google.common.hash.HashCode
|
||||
import com.google.common.hash.Hashing
|
||||
import com.google.common.hash.HashingInputStream
|
||||
import com.google.common.io.CountingInputStream
|
||||
import net.corda.core.ClientRelevantError
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
import net.corda.core.node.services.vault.AttachmentSort
|
||||
@ -30,6 +33,7 @@ import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Paths
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.jar.JarInputStream
|
||||
@ -45,6 +49,10 @@ class NodeAttachmentService(
|
||||
cacheFactory: NamedCacheFactory,
|
||||
private val database: CordaPersistence
|
||||
) : AttachmentStorageInternal, SingletonSerializeAsToken() {
|
||||
|
||||
// This is to break the circular dependency.
|
||||
lateinit var servicesForResolution: ServicesForResolution
|
||||
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
|
||||
@ -94,7 +102,13 @@ class NodeAttachmentService(
|
||||
@Column(name = "contract_class_name", nullable = false)
|
||||
@CollectionTable(name = "${NODE_DATABASE_PREFIX}attachments_contracts", joinColumns = [(JoinColumn(name = "att_id", referencedColumnName = "att_id"))],
|
||||
foreignKey = ForeignKey(name = "FK__ctr_class__attachments"))
|
||||
var contractClassNames: List<ContractClassName>? = null
|
||||
var contractClassNames: List<ContractClassName>? = null,
|
||||
|
||||
@ElementCollection(targetClass = PublicKey::class, fetch = FetchType.EAGER)
|
||||
@Column(name = "signer", nullable = false)
|
||||
@CollectionTable(name = "${NODE_DATABASE_PREFIX}attachments_signers", joinColumns = [(JoinColumn(name = "att_id", referencedColumnName = "att_id"))],
|
||||
foreignKey = ForeignKey(name = "FK__signers__attachments"))
|
||||
var signers: List<PublicKey>? = null
|
||||
)
|
||||
|
||||
@VisibleForTesting
|
||||
@ -212,11 +226,13 @@ class NodeAttachmentService(
|
||||
|
||||
private fun loadAttachmentContent(id: SecureHash): Pair<Attachment, ByteArray>? {
|
||||
return database.transaction {
|
||||
val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) ?: return@transaction null
|
||||
val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString())
|
||||
?: return@transaction null
|
||||
val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let {
|
||||
val contracts = attachment.contractClassNames
|
||||
if (contracts != null && contracts.isNotEmpty()) {
|
||||
ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader)
|
||||
ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader, attachment.signers
|
||||
?: emptyList())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
@ -290,14 +306,19 @@ class NodeAttachmentService(
|
||||
val id = bytes.sha256()
|
||||
if (!hasAttachment(id)) {
|
||||
checkIsAValidJAR(bytes.inputStream())
|
||||
|
||||
val jarSigners = getSigners(bytes)
|
||||
|
||||
val session = currentDBSession()
|
||||
val attachment = NodeAttachmentService.DBAttachment(
|
||||
attId = id.toString(),
|
||||
content = bytes,
|
||||
uploader = uploader,
|
||||
filename = filename,
|
||||
contractClassNames = contractClassNames
|
||||
contractClassNames = contractClassNames,
|
||||
signers = jarSigners
|
||||
)
|
||||
|
||||
session.save(attachment)
|
||||
attachmentCount.inc()
|
||||
log.info("Stored new attachment $id")
|
||||
@ -309,6 +330,9 @@ class NodeAttachmentService(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSigners(attachmentBytes: ByteArray) =
|
||||
JarSignatureCollector.collectSigners(JarInputStream(attachmentBytes.inputStream()))
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
override fun importOrGetAttachment(jar: InputStream): AttachmentId {
|
||||
return try {
|
||||
@ -339,4 +363,4 @@ class NodeAttachmentService(
|
||||
query.resultList.map { AttachmentId.parse(it.attId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package net.corda.node.services.persistence
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.utilities.hexToByteArray
|
||||
import net.corda.core.utilities.toHex
|
||||
import java.security.PublicKey
|
||||
import javax.persistence.AttributeConverter
|
||||
import javax.persistence.Converter
|
||||
|
||||
/**
|
||||
* Converts to and from a Public key into a hex encoded string.
|
||||
* Used by JPA to automatically map a [PublicKey] to a text column
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
class PublicKeyToTextConverter : AttributeConverter<PublicKey, String> {
|
||||
override fun convertToDatabaseColumn(key: PublicKey?): String? = key?.encoded?.toHex()
|
||||
override fun convertToEntityAttribute(text: String?): PublicKey? = text?.let { Crypto.decodePublicKey(it.hexToByteArray()) }
|
||||
}
|
@ -505,7 +505,8 @@ class NodeVaultService(
|
||||
// Even if we set the default pageNumber to be 1 instead, that may not cover the non-default cases.
|
||||
// So the floor may be necessary anyway.
|
||||
query.firstResult = maxOf(0, (paging.pageNumber - 1) * paging.pageSize)
|
||||
query.maxResults = paging.pageSize + 1 // detection too many results
|
||||
val pageSize = paging.pageSize + 1
|
||||
query.maxResults = if (pageSize > 0) pageSize else Integer.MAX_VALUE // detection too many results, protected against overflow
|
||||
|
||||
// execution
|
||||
val results = query.resultList
|
||||
|
@ -14,4 +14,17 @@
|
||||
<renameTable oldTableName="NODE_ATTACHMENTS_CONTRACT_CLASS_NAME" newTableName="NODE_ATTACHMENTS_CONTRACTS" />
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="R3.Corda" id="add_signers">
|
||||
<createTable tableName="node_attachments_signers">
|
||||
<column name="att_id" type="NVARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="signer" type="NVARCHAR(1024)"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseColumnNames="att_id" baseTableName="node_attachments_signers"
|
||||
constraintName="FK__signers__attachments"
|
||||
referencedColumnNames="att_id" referencedTableName="node_attachments"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
@ -2,6 +2,8 @@ package net.corda.node.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.node.JavaPackageName
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
@ -10,6 +12,7 @@ import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
@ -65,7 +68,8 @@ class NetworkParametersTest {
|
||||
fun `choosing notary not specified in network parameters will fail`() {
|
||||
val fakeNotary = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME, configOverrides = {
|
||||
val notary = NotaryConfig(false)
|
||||
doReturn(notary).whenever(it).notary}))
|
||||
doReturn(notary).whenever(it).notary
|
||||
}))
|
||||
val fakeNotaryId = fakeNotary.info.singleIdentity()
|
||||
val alice = mockNet.createPartyNode(ALICE_NAME)
|
||||
assertThat(alice.services.networkMapCache.notaryIdentities).doesNotContain(fakeNotaryId)
|
||||
@ -87,6 +91,62 @@ class NetworkParametersTest {
|
||||
}.withMessage("maxTransactionSize cannot be bigger than maxMessageSize")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `package ownership checks are correct`() {
|
||||
val key1 = generateKeyPair().public
|
||||
val key2 = generateKeyPair().public
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
||||
NetworkParameters(1,
|
||||
emptyList(),
|
||||
2001,
|
||||
2000,
|
||||
Instant.now(),
|
||||
1,
|
||||
emptyMap(),
|
||||
Int.MAX_VALUE.days,
|
||||
mapOf(
|
||||
JavaPackageName("com.!example.stuff") to key2
|
||||
)
|
||||
)
|
||||
}.withMessageContaining("Attempting to whitelist illegal java package")
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
||||
NetworkParameters(1,
|
||||
emptyList(),
|
||||
2001,
|
||||
2000,
|
||||
Instant.now(),
|
||||
1,
|
||||
emptyMap(),
|
||||
Int.MAX_VALUE.days,
|
||||
mapOf(
|
||||
JavaPackageName("com.example") to key1,
|
||||
JavaPackageName("com.example.stuff") to key2
|
||||
)
|
||||
)
|
||||
}.withMessage("multiple packages added to the packageOwnership overlap.")
|
||||
|
||||
NetworkParameters(1,
|
||||
emptyList(),
|
||||
2001,
|
||||
2000,
|
||||
Instant.now(),
|
||||
1,
|
||||
emptyMap(),
|
||||
Int.MAX_VALUE.days,
|
||||
mapOf(
|
||||
JavaPackageName("com.example") to key1,
|
||||
JavaPackageName("com.examplestuff") to key2
|
||||
)
|
||||
)
|
||||
|
||||
assert(JavaPackageName("com.example").owns("com.example.something.MyClass"))
|
||||
assert(!JavaPackageName("com.example").owns("com.examplesomething.MyClass"))
|
||||
assert(!JavaPackageName("com.exam").owns("com.example.something.MyClass"))
|
||||
|
||||
}
|
||||
|
||||
// Helpers
|
||||
private fun dropParametersToDir(dir: Path, params: NetworkParameters) {
|
||||
NetworkParametersCopier(params).install(dir)
|
||||
|
@ -4,10 +4,16 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.google.common.jimfs.Configuration
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.JarSignatureTestUtils.createJar
|
||||
import net.corda.core.JarSignatureTestUtils.generateKey
|
||||
import net.corda.core.JarSignatureTestUtils.signJar
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
import net.corda.core.node.services.vault.AttachmentSort
|
||||
import net.corda.core.node.services.vault.Builder
|
||||
@ -17,33 +23,40 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.configureDatabase
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.URI
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.*
|
||||
import java.security.PublicKey
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarOutputStream
|
||||
import javax.tools.JavaFileObject
|
||||
import javax.tools.SimpleJavaFileObject
|
||||
import javax.tools.StandardLocation
|
||||
import javax.tools.ToolProvider
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
|
||||
|
||||
class NodeAttachmentServiceTest {
|
||||
|
||||
// Use an in memory file system for testing attachment storage.
|
||||
private lateinit var fs: FileSystem
|
||||
private lateinit var database: CordaPersistence
|
||||
private lateinit var storage: NodeAttachmentService
|
||||
private val services = rigorousMock<ServicesForResolution>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@ -52,18 +65,40 @@ class NodeAttachmentServiceTest {
|
||||
val dataSourceProperties = makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null })
|
||||
fs = Jimfs.newFileSystem(Configuration.unix())
|
||||
|
||||
doReturn(testNetworkParameters()).whenever(services).networkParameters
|
||||
|
||||
storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also {
|
||||
database.transaction {
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
storage.servicesForResolution = services
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
dir.list { subdir ->
|
||||
subdir.forEach(Path::deleteRecursively)
|
||||
}
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importing a signed jar saves the signers to the storage`() {
|
||||
val jarAndSigner = makeTestSignedContractJar("com.example.MyContract")
|
||||
val signedJar = jarAndSigner.first
|
||||
val attachmentId = storage.importAttachment(signedJar.inputStream(), "test", null)
|
||||
assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signers.map { it.hash })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importing a non-signed jar will save no signers`() {
|
||||
val jarName = makeTestContractJar("com.example.MyContract")
|
||||
val attachmentId = storage.importAttachment(dir.resolve(jarName).inputStream(), "test", null)
|
||||
assertEquals(0, storage.openAttachment(attachmentId)!!.signers.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert and retrieve`() {
|
||||
val (testJar, expectedHash) = makeTestJar()
|
||||
@ -289,7 +324,20 @@ class NodeAttachmentServiceTest {
|
||||
return Pair(file, file.readAll().sha256())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
companion object {
|
||||
private val dir = Files.createTempDirectory(NodeAttachmentServiceTest::class.simpleName)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun beforeClass() {
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic
|
||||
fun afterClass() {
|
||||
dir.deleteRecursively()
|
||||
}
|
||||
|
||||
private fun makeTestJar(output: OutputStream, extraEntries: List<Pair<String, String>> = emptyList()) {
|
||||
output.use {
|
||||
val jar = JarOutputStream(it)
|
||||
@ -305,5 +353,48 @@ class NodeAttachmentServiceTest {
|
||||
jar.closeEntry()
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeTestSignedContractJar(contractName: String): Pair<Path, PublicKey> {
|
||||
val alias = "testAlias"
|
||||
val pwd = "testPassword"
|
||||
dir.generateKey(alias, pwd, ALICE_NAME.toString())
|
||||
val jarName = makeTestContractJar(contractName)
|
||||
val signer = dir.signJar(jarName, alias, pwd)
|
||||
return dir.resolve(jarName) to signer
|
||||
}
|
||||
|
||||
private fun makeTestContractJar(contractName: String): String {
|
||||
val packages = contractName.split(".")
|
||||
val jarName = "testattachment.jar"
|
||||
val className = packages.last()
|
||||
createTestClass(className, packages.subList(0, packages.size - 1))
|
||||
dir.createJar(jarName, "${contractName.replace(".", "/")}.class")
|
||||
return jarName
|
||||
}
|
||||
|
||||
private fun createTestClass(className: String, packages: List<String>): Path {
|
||||
val newClass = """package ${packages.joinToString(".")};
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.transactions.*;
|
||||
|
||||
public class $className implements Contract {
|
||||
@Override
|
||||
public void verify(LedgerTransaction tx) throws IllegalArgumentException {
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val compiler = ToolProvider.getSystemJavaCompiler()
|
||||
val source = object : SimpleJavaFileObject(URI.create("string:///${packages.joinToString("/")}/${className}.java"), JavaFileObject.Kind.SOURCE) {
|
||||
override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence {
|
||||
return newClass
|
||||
}
|
||||
}
|
||||
val fileManager = compiler.getStandardFileManager(null, null, null)
|
||||
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(dir.toFile()))
|
||||
|
||||
val compile = compiler.getTask(System.out.writer(), fileManager, null, null, null, listOf(source)).call()
|
||||
return Paths.get(fileManager.list(StandardLocation.CLASS_OUTPUT, "", setOf(JavaFileObject.Kind.CLASS), true).single().name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user