From bd89da458bd730ad2af1eead69f3473badeba0f1 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 4 Nov 2016 11:33:43 +0000 Subject: [PATCH] Added nice extension methods for Path, which are more readable than the static methods from Files --- core/build.gradle | 1 + .../src/main/kotlin/com/r3corda/core/Utils.kt | 37 +++++++++++----- .../com/r3corda/core/crypto/X509Utilities.kt | 21 ++++----- .../r3corda/core/crypto/X509UtilitiesTest.kt | 33 ++++++-------- node/build.gradle | 1 - .../com/r3corda/node/internal/AbstractNode.kt | 23 +++++----- .../kotlin/com/r3corda/node/internal/Node.kt | 3 +- .../r3corda/node/services/RPCUserService.kt | 4 +- .../node/services/config/ConfigUtilities.kt | 10 +++-- .../messaging/ArtemisMessagingComponent.kt | 6 +-- .../persistence/NodeAttachmentService.kt | 33 +++++++------- .../certsigning/CertificateSigner.kt | 12 +++-- .../r3corda/node/messaging/AttachmentTests.kt | 9 +--- .../services/NodeAttachmentStorageTest.kt | 44 +++++++++++-------- .../PropertiesFileRPCUserServiceTest.kt | 4 +- .../certsigning/CertificateSignerTest.kt | 13 +++--- .../com/r3corda/testing/node/MockNode.kt | 10 ++--- 17 files changed, 135 insertions(+), 129 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 2d7c7fdfad..7d89235caf 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -44,6 +44,7 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2" compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // Thread safety annotations diff --git a/core/src/main/kotlin/com/r3corda/core/Utils.kt b/core/src/main/kotlin/com/r3corda/core/Utils.kt index 86c4b29e51..72bc1ca13c 100644 --- a/core/src/main/kotlin/com/r3corda/core/Utils.kt +++ b/core/src/main/kotlin/com/r3corda/core/Utils.kt @@ -8,6 +8,7 @@ import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.SettableFuture import com.r3corda.core.crypto.newSecureRandom +import kotlinx.support.jdk7.use import org.slf4j.Logger import rx.Observable import rx.subjects.UnicastSubject @@ -15,16 +16,16 @@ import java.io.BufferedInputStream import java.io.InputStream import java.io.OutputStream import java.math.BigDecimal -import java.nio.file.Files -import java.nio.file.LinkOption -import java.nio.file.OpenOption -import java.nio.file.Path +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.* import java.nio.file.attribute.FileAttribute import java.time.Duration import java.time.temporal.Temporal import java.util.concurrent.ExecutionException import java.util.concurrent.Executor import java.util.concurrent.locks.ReentrantLock +import java.util.stream.Stream import java.util.zip.ZipInputStream import kotlin.concurrent.withLock import kotlin.reflect.KProperty @@ -95,15 +96,27 @@ inline fun SettableFuture.catch(block: () -> T) { /** Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform separator problems. */ operator fun Path.div(other: String): Path = resolve(other) +fun Path.createDirectory(vararg attrs: FileAttribute<*>): Path = Files.createDirectory(this, *attrs) fun Path.createDirectories(vararg attrs: FileAttribute<*>): Path = Files.createDirectories(this, *attrs) fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options) -inline fun Path.use(block: (InputStream) -> R): R = Files.newInputStream(this).use(block) -inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption, block: (OutputStream) -> Unit) { +fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options) +fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options) +fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options) +val Path.size: Long get() = Files.size(this) +inline fun Path.list(block: (Stream) -> R): R = Files.list(this).use(block) +fun Path.deleteIfExists(): Boolean = Files.deleteIfExists(this) +fun Path.readAll(): ByteArray = Files.readAllBytes(this) +inline fun Path.read(vararg options: OpenOption, block: (InputStream) -> R): R = Files.newInputStream(this, *options).use(block) +inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption = emptyArray(), block: (OutputStream) -> Unit) { if (createDirs) { normalize().parent?.createDirectories() } Files.newOutputStream(this, *options).use(block) } +inline fun Path.readLines(charset: Charset = UTF_8, block: (Stream) -> R): R = Files.lines(this, charset).use(block) +fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options) + +fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) // Simple infix function to add back null safety that the JDK lacks: timeA until timeB infix fun Temporal.until(endExclusive: Temporal) = Duration.between(this, endExclusive) @@ -213,23 +226,23 @@ class TransientProperty(private val initializer: () -> T) { */ fun extractZipFile(zipPath: Path, toPath: Path) { val normalisedToPath = toPath.normalize() - if (!Files.exists(normalisedToPath)) - Files.createDirectories(normalisedToPath) + normalisedToPath.createDirectories() - ZipInputStream(BufferedInputStream(Files.newInputStream(zipPath))).use { zip -> + zipPath.read { + val zip = ZipInputStream(BufferedInputStream(it)) while (true) { val e = zip.nextEntry ?: break - val outPath = normalisedToPath.resolve(e.name) + val outPath = normalisedToPath / e.name // Security checks: we should reject a zip that contains tricksy paths that try to escape toPath. if (!outPath.normalize().startsWith(normalisedToPath)) throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}") if (e.isDirectory) { - Files.createDirectories(outPath) + outPath.createDirectories() continue } - Files.newOutputStream(outPath).use { out -> + outPath.write { out -> ByteStreams.copy(zip, out) } zip.closeEntry() diff --git a/core/src/main/kotlin/com/r3corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/com/r3corda/core/crypto/X509Utilities.kt index 0bfaf5abad..ed0785d445 100644 --- a/core/src/main/kotlin/com/r3corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/com/r3corda/core/crypto/X509Utilities.kt @@ -1,7 +1,9 @@ package com.r3corda.core.crypto +import com.r3corda.core.exists import com.r3corda.core.random63BitValue -import com.r3corda.core.use +import com.r3corda.core.read +import com.r3corda.core.write import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1EncodableVector import org.bouncycastle.asn1.DERSequence @@ -27,7 +29,6 @@ import java.io.FileWriter import java.io.InputStream import java.math.BigInteger import java.net.InetAddress -import java.nio.file.Files import java.nio.file.Path import java.security.* import java.security.cert.Certificate @@ -135,14 +136,11 @@ object X509Utilities { fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { val pass = storePassword.toCharArray() val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - if (Files.exists(keyStoreFilePath)) { - keyStoreFilePath.use { keyStore.load(it, pass) } + if (keyStoreFilePath.exists()) { + keyStoreFilePath.read { keyStore.load(it, pass) } } else { keyStore.load(null, pass) - val output = Files.newOutputStream(keyStoreFilePath) - output.use { - keyStore.store(output, pass) - } + keyStoreFilePath.write { keyStore.store(it, pass) } } return keyStore } @@ -157,7 +155,7 @@ object X509Utilities { fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { val pass = storePassword.toCharArray() val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - keyStoreFilePath.use { keyStore.load(it, pass) } + keyStoreFilePath.read { keyStore.load(it, pass) } return keyStore } @@ -186,10 +184,7 @@ object X509Utilities { */ fun saveKeyStore(keyStore: KeyStore, keyStoreFilePath: Path, storePassword: String) { val pass = storePassword.toCharArray() - val output = Files.newOutputStream(keyStoreFilePath) - output.use { - keyStore.store(output, pass) - } + keyStoreFilePath.write { keyStore.store(it, pass) } } /** diff --git a/core/src/test/kotlin/com/r3corda/core/crypto/X509UtilitiesTest.kt b/core/src/test/kotlin/com/r3corda/core/crypto/X509UtilitiesTest.kt index 1c8afa418d..74bcc79547 100644 --- a/core/src/test/kotlin/com/r3corda/core/crypto/X509UtilitiesTest.kt +++ b/core/src/test/kotlin/com/r3corda/core/crypto/X509UtilitiesTest.kt @@ -1,5 +1,6 @@ package com.r3corda.core.crypto +import com.r3corda.core.div import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x509.GeneralName @@ -11,6 +12,7 @@ import java.io.DataOutputStream import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress +import java.nio.file.Path import java.security.PrivateKey import java.security.SecureRandom import java.security.Signature @@ -38,18 +40,15 @@ class X509UtilitiesTest { assertTrue { caCertAndKey.certificate.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate } - @Test fun `load and save a PEM file certificate`() { - val tmpCertificateFile = tempFolder.root.toPath().resolve("cacert.pem") - + val tmpCertificateFile = tempFile("cacert.pem") val caCertAndKey = X509Utilities.createSelfSignedCACert("Test Cert") X509Utilities.saveCertificateAsPEMFile(caCertAndKey.certificate, tmpCertificateFile) val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile) assertEquals(caCertAndKey.certificate, readCertificate) } - @Test fun `create valid server certificate chain`() { val caCertAndKey = X509Utilities.createSelfSignedCACert("Test CA Cert") @@ -84,15 +83,11 @@ class X509UtilitiesTest { @Test fun `create full CA keystore`() { - val tmpKeyStore = tempFolder.root.toPath().resolve("keystore.jks") - val tmpTrustStore = tempFolder.root.toPath().resolve("truststore.jks") + val tmpKeyStore = tempFile("keystore.jks") + val tmpTrustStore = tempFile("truststore.jks") // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store - X509Utilities.createCAKeyStoreAndTrustStore(tmpKeyStore, - "keystorepass", - "keypass", - tmpTrustStore, - "trustpass") + X509Utilities.createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass") // Load back generated root CA Cert and private key from keystore and check against copy in truststore val keyStore = X509Utilities.loadKeyStore(tmpKeyStore, "keystorepass") @@ -132,12 +127,11 @@ class X509UtilitiesTest { assertTrue { intermediateVerifier.verify(intermediateSignature) } } - @Test fun `create server certificate in keystore for SSL`() { - val tmpCAKeyStore = tempFolder.root.toPath().resolve("keystore.jks") - val tmpTrustStore = tempFolder.root.toPath().resolve("truststore.jks") - val tmpServerKeyStore = tempFolder.root.toPath().resolve("serverkeystore.jks") + val tmpCAKeyStore = tempFile("keystore.jks") + val tmpTrustStore = tempFile("truststore.jks") + val tmpServerKeyStore = tempFile("serverkeystore.jks") // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store X509Utilities.createCAKeyStoreAndTrustStore(tmpCAKeyStore, @@ -177,9 +171,9 @@ class X509UtilitiesTest { @Test fun `create server cert and use in SSL socket`() { - val tmpCAKeyStore = tempFolder.root.toPath().resolve("keystore.jks") - val tmpTrustStore = tempFolder.root.toPath().resolve("truststore.jks") - val tmpServerKeyStore = tempFolder.root.toPath().resolve("serverkeystore.jks") + val tmpCAKeyStore = tempFile("keystore.jks") + val tmpTrustStore = tempFile("truststore.jks") + val tmpServerKeyStore = tempFile("serverkeystore.jks") // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store val caKeyStore = X509Utilities.createCAKeyStoreAndTrustStore(tmpCAKeyStore, @@ -218,7 +212,6 @@ class X509UtilitiesTest { serverSocket.sslParameters = serverParams serverSocket.useClientMode = false - val clientSocket = clientSocketFactory.createSocket() as SSLSocket val clientParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", @@ -285,4 +278,6 @@ class X509UtilitiesTest { serverSocket.close() assertTrue(done) } + + private fun tempFile(name: String): Path = tempFolder.root.toPath() / name } diff --git a/node/build.gradle b/node/build.gradle index d1f9804919..da08ce70b7 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -56,7 +56,6 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2" compile "com.google.guava:guava:19.0" diff --git a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt index 4f3de4ed89..819314b93e 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt @@ -4,6 +4,7 @@ import com.codahale.metrics.MetricRegistry import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.SettableFuture +import com.r3corda.core.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.X509Utilities import com.r3corda.core.messaging.SingleMessageRecipient @@ -12,7 +13,6 @@ import com.r3corda.core.node.services.* import com.r3corda.core.node.services.NetworkMapCache.MapChangeType import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.protocols.ProtocolLogicRefFactory -import com.r3corda.core.seconds import com.r3corda.core.serialization.SingletonSerializeAsToken import com.r3corda.core.serialization.deserialize import com.r3corda.core.serialization.serialize @@ -47,7 +47,6 @@ import com.r3corda.protocols.sendRequest import org.jetbrains.exposed.sql.Database import org.slf4j.Logger import java.nio.file.FileAlreadyExistsException -import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair import java.time.Clock @@ -468,11 +467,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo // "permissioned". The identity file is what gets distributed and contains the node's legal name along with // the public key. Obviously in a real system this would need to be a certificate chain of some kind to ensure // the legal name is actually validated in some way. - val privKeyFile = dir.resolve(privateKeyFileName) - val pubIdentityFile = dir.resolve(publicKeyFileName) + val privKeyFile = dir / privateKeyFileName + val pubIdentityFile = dir / publicKeyFileName val identityName = if (serviceName == null) configuration.myLegalName else configuration.myLegalName + "|" + serviceName - val identityAndKey = if (!Files.exists(privKeyFile)) { + val identityAndKey = if (!privKeyFile.exists()) { log.info("Identity key not found, generating fresh key!") val keyPair: KeyPair = generateKeyPair() keyPair.serialize().writeToFile(privKeyFile) @@ -485,12 +484,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo // Check that the identity in the config file matches the identity file we have stored to disk. // This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed // things up for us. - val myIdentity = Files.readAllBytes(pubIdentityFile).deserialize() + val myIdentity = pubIdentityFile.readAll().deserialize() if (myIdentity.name != identityName) throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" + - "${identityName} vs ${myIdentity.name}") + "$identityName vs ${myIdentity.name}") // Load the private key. - val keyPair = Files.readAllBytes(privKeyFile).deserialize() + val keyPair = privKeyFile.readAll().deserialize() Pair(myIdentity, keyPair) } partyKeys += identityAndKey.second @@ -500,17 +499,15 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo protected open fun generateKeyPair() = com.r3corda.core.crypto.generateKeyPair() protected fun makeAttachmentStorage(dir: Path): NodeAttachmentService { - val attachmentsDir = dir.resolve("attachments") + val attachmentsDir = dir / "attachments" try { - Files.createDirectory(attachmentsDir) + attachmentsDir.createDirectory() } catch (e: FileAlreadyExistsException) { } return NodeAttachmentService(attachmentsDir, services.monitoringService.metrics) } protected fun createNodeDir() { - if (!Files.exists(configuration.basedir)) { - Files.createDirectories(configuration.basedir) - } + configuration.basedir.createDirectories() } } diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index 814f9fd3ab..56459083c0 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -1,6 +1,7 @@ package com.r3corda.node.internal import com.codahale.metrics.JmxReporter +import com.r3corda.core.div import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.services.ServiceInfo @@ -366,7 +367,7 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress: // file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already // exists, we try to take the file lock first before replacing it and if that fails it means we're being started // twice with the same directory: that's a user error and we should bail out. - val pidPath = configuration.basedir.resolve("process-id") + val pidPath = configuration.basedir / "process-id" val file = pidPath.toFile() if (!file.exists()) { file.createNewFile() diff --git a/node/src/main/kotlin/com/r3corda/node/services/RPCUserService.kt b/node/src/main/kotlin/com/r3corda/node/services/RPCUserService.kt index aaa5d62c77..a59a48380b 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/RPCUserService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/RPCUserService.kt @@ -1,7 +1,7 @@ package com.r3corda.node.services import com.r3corda.core.exists -import com.r3corda.core.use +import com.r3corda.core.read import java.nio.file.Path import java.util.* @@ -25,7 +25,7 @@ class PropertiesFileRPCUserService(file: Path) : RPCUserService { init { _users = if (file.exists()) { val properties = Properties() - file.use { + file.read { properties.load(it) } properties.map { diff --git a/node/src/main/kotlin/com/r3corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/com/r3corda/node/services/config/ConfigUtilities.kt index effa18ddb0..b5baecd707 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/config/ConfigUtilities.kt @@ -1,7 +1,10 @@ package com.r3corda.node.services.config import com.google.common.net.HostAndPort +import com.r3corda.core.copyTo +import com.r3corda.core.createDirectories import com.r3corda.core.crypto.X509Utilities +import com.r3corda.core.div import com.r3corda.core.exists import com.r3corda.core.utilities.loggerFor import com.typesafe.config.Config @@ -29,7 +32,7 @@ object ConfigHelper { val defaultConfig = ConfigFactory.parseResources("reference.conf", ConfigParseOptions.defaults().setAllowMissing(false)) val normalisedBaseDir = baseDirectoryPath.normalize() - val configFile = (configFileOverride?.normalize() ?: normalisedBaseDir.resolve("node.conf")).toFile() + val configFile = (configFileOverride?.normalize() ?: normalisedBaseDir / "node.conf").toFile() val appConfig = ConfigFactory.parseFile(configFile, ConfigParseOptions.defaults().setAllowMissing(allowMissingConfig)) val overridesMap = HashMap() // If we do require a few other command line overrides eg for a nicer development experience they would go inside this map. @@ -89,10 +92,9 @@ fun Config.getProperties(path: String): Properties { * the CA certs in Node resources. Then provision KeyStores into certificates folder under node path. */ fun NodeSSLConfiguration.configureWithDevSSLCertificate() { - Files.createDirectories(certificatesPath) + certificatesPath.createDirectories() if (!trustStorePath.exists()) { - Files.copy(javaClass.classLoader.getResourceAsStream("com/r3corda/node/internal/certificates/cordatruststore.jks"), - trustStorePath) + javaClass.classLoader.getResourceAsStream("com/r3corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStorePath) } if (!keyStorePath.exists()) { val caKeyStore = X509Utilities.loadKeyStore( diff --git a/node/src/main/kotlin/com/r3corda/node/services/messaging/ArtemisMessagingComponent.kt b/node/src/main/kotlin/com/r3corda/node/services/messaging/ArtemisMessagingComponent.kt index 53138ce5bb..1ce07a3282 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/messaging/ArtemisMessagingComponent.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/messaging/ArtemisMessagingComponent.kt @@ -7,7 +7,7 @@ import com.r3corda.core.crypto.toBase58String import com.r3corda.core.messaging.MessageRecipients import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.core.serialization.SingletonSerializeAsToken -import com.r3corda.core.use +import com.r3corda.core.read import com.r3corda.node.services.config.NodeSSLConfiguration import com.r3corda.node.services.config.configureWithDevSSLCertificate import org.apache.activemq.artemis.api.core.SimpleString @@ -110,10 +110,10 @@ abstract class ArtemisMessagingComponent() : SingletonSerializeAsToken() { * unfortunately Artemis tends to bury the exception when the password is wrong. */ fun checkStorePasswords() { - config.keyStorePath.use { + config.keyStorePath.read { KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray()) } - config.trustStorePath.use { + config.trustStorePath.read { KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray()) } } diff --git a/node/src/main/kotlin/com/r3corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/com/r3corda/node/services/persistence/NodeAttachmentService.kt index 018803a73e..110b7b8f88 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/persistence/NodeAttachmentService.kt @@ -5,9 +5,9 @@ import com.google.common.annotations.VisibleForTesting import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream import com.google.common.io.CountingInputStream +import com.r3corda.core.* import com.r3corda.core.contracts.Attachment import com.r3corda.core.crypto.SecureHash -import com.r3corda.core.extractZipFile import com.r3corda.core.node.services.AttachmentStorage import com.r3corda.core.utilities.loggerFor import com.r3corda.node.services.api.AcceptsFileUpload @@ -35,7 +35,7 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta } // Just count all non-directories in the attachment store, and assume the admin hasn't dumped any junk there. - private fun countAttachments() = Files.list(storePath).filter { Files.isRegularFile(it) }.count() + private fun countAttachments() = storePath.list { it.filter { it.isRegularFile() }.count() } /** * If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for @@ -45,7 +45,7 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta @Volatile var automaticallyExtractAttachments = false init { - require(Files.isDirectory(storePath)) { "$storePath must be a directory" } + require(storePath.isDirectory()) { "$storePath must be a directory" } } class OnDiskHashMismatch(val file: Path, val actual: SecureHash) : Exception() { @@ -66,7 +66,7 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta private val counter: CountingInputStream = CountingInputStream(input), private val stream: HashingInputStream = HashingInputStream(Hashing.sha256(), counter)) : FilterInputStream(stream) { - private val expectedSize = Files.size(filePath) + private val expectedSize = filePath.size override fun close() { super.close() @@ -94,8 +94,8 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta } override fun openAttachment(id: SecureHash): Attachment? { - val path = storePath.resolve(id.toString()) - if (!Files.exists(path)) return null + val path = storePath / id.toString() + if (!path.exists()) return null return AttachmentImpl(id, path, checkAttachmentsOnLoad) } @@ -103,28 +103,28 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta override fun importAttachment(jar: InputStream): SecureHash { require(jar !is JarInputStream) val hs = HashingInputStream(Hashing.sha256(), jar) - val tmp = storePath.resolve("tmp.${UUID.randomUUID()}") - Files.copy(hs, tmp) + val tmp = storePath / "tmp.${UUID.randomUUID()}" + hs.copyTo(tmp) checkIsAValidJAR(tmp) val id = SecureHash.SHA256(hs.hash().asBytes()) - val finalPath = storePath.resolve(id.toString()) + val finalPath = storePath / id.toString() try { // Move into place atomically or fail if that isn't possible. We don't want a half moved attachment to // be exposed to parallel threads. This gives us thread safety. - if (!Files.exists(finalPath)) { + if (!finalPath.exists()) { log.info("Stored new attachment $id") attachmentCount.inc() } else { log.info("Replacing attachment $id - only bother doing this if you're trying to repair file corruption") } - Files.move(tmp, finalPath, StandardCopyOption.ATOMIC_MOVE) + tmp.moveTo(finalPath, StandardCopyOption.ATOMIC_MOVE) } finally { - Files.deleteIfExists(tmp) + tmp.deleteIfExists() } if (automaticallyExtractAttachments) { - val extractTo = storePath.resolve("$id.jar") + val extractTo = storePath / "$id.jar" try { - Files.createDirectory(extractTo) + extractTo.createDirectory() extractZipFile(finalPath, extractTo) } catch(e: FileAlreadyExistsException) { log.trace("Did not extract attachment jar to directory because it already exists") @@ -138,9 +138,10 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta private fun checkIsAValidJAR(path: Path) { // Just iterate over the entries with verification enabled: should be good enough to catch mistakes. - JarInputStream(Files.newInputStream(path), true).use { stream -> + path.read { + val jar = JarInputStream(it) while (true) { - val cursor = stream.nextJarEntry ?: break + val cursor = jar.nextJarEntry ?: break val entryPath = Paths.get(cursor.name) // Security check to stop zips trying to escape their rightful place. if (entryPath.isAbsolute || entryPath.normalize() != entryPath || '\\' in cursor.name) diff --git a/node/src/main/kotlin/com/r3corda/node/utilities/certsigning/CertificateSigner.kt b/node/src/main/kotlin/com/r3corda/node/utilities/certsigning/CertificateSigner.kt index b7b6af1218..e5cf67cc71 100644 --- a/node/src/main/kotlin/com/r3corda/node/utilities/certsigning/CertificateSigner.kt +++ b/node/src/main/kotlin/com/r3corda/node/utilities/certsigning/CertificateSigner.kt @@ -1,13 +1,12 @@ package com.r3corda.node.utilities.certsigning +import com.r3corda.core.* import com.r3corda.core.crypto.X509Utilities import com.r3corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import com.r3corda.core.crypto.X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY import com.r3corda.core.crypto.X509Utilities.CORDA_ROOT_CA import com.r3corda.core.crypto.X509Utilities.addOrReplaceCertificate import com.r3corda.core.crypto.X509Utilities.addOrReplaceKey -import com.r3corda.core.div -import com.r3corda.core.minutes import com.r3corda.core.utilities.loggerFor import com.r3corda.node.services.config.ConfigHelper import com.r3corda.node.services.config.FullNodeConfiguration @@ -15,7 +14,6 @@ import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.config.getValue import joptsimple.OptionParser import java.net.URL -import java.nio.file.Files import java.nio.file.Paths import java.security.KeyPair import java.security.cert.Certificate @@ -34,7 +32,7 @@ class CertificateSigner(val config: NodeConfiguration, val certService: Certific } fun buildKeyStore() { - Files.createDirectories(config.certificatesPath) + config.certificatesPath.createDirectories() val caKeyStore = X509Utilities.loadOrCreateKeyStore(config.keyStorePath, config.keyStorePassword) @@ -101,15 +99,15 @@ class CertificateSigner(val config: NodeConfiguration, val certService: Certific private fun submitCertificateSigningRequest(keyPair: KeyPair): String { val requestIdStore = config.certificatesPath / "certificate-request-id.txt" // Retrieve request id from file if exists, else post a request to server. - return if (!Files.exists(requestIdStore)) { + return if (!requestIdStore.exists()) { val request = X509Utilities.createCertificateSigningRequest(config.myLegalName, config.nearestCity, config.emailAddress, keyPair) // Post request to signing server via http. val requestId = certService.submitRequest(request) // Persists request ID to file in case of node shutdown. - Files.write(requestIdStore, listOf(requestId), Charsets.UTF_8) + requestIdStore.writeLines(listOf(requestId)) requestId } else { - Files.readAllLines(requestIdStore).first() + requestIdStore.readLines { it.findFirst().get() } } } } diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/AttachmentTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/AttachmentTests.kt index c05e11363d..fa4772b519 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/AttachmentTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/AttachmentTests.kt @@ -5,7 +5,7 @@ import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.sha256 import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.core.node.services.ServiceInfo -import com.r3corda.core.serialization.OpaqueBytes +import com.r3corda.core.write import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.persistence.NodeAttachmentService @@ -18,9 +18,6 @@ import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.nio.ByteBuffer -import java.nio.file.Files -import java.nio.file.StandardOpenOption import java.security.KeyPair import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -103,9 +100,7 @@ class AttachmentTests { val id = n0.storage.attachments.importAttachment(ByteArrayInputStream(fakeAttachment())) // Corrupt its store. - val writer = Files.newByteChannel(network.filesystem.getPath("/nodes/0/attachments/$id"), StandardOpenOption.WRITE) - writer.write(ByteBuffer.wrap(OpaqueBytes.of(99, 99, 99, 99).bits)) - writer.close() + network.filesystem.getPath("/nodes/0/attachments/$id").write { it.write(byteArrayOf(99, 99, 99, 99)) } // Get n1 to fetch the attachment. Should receive corrupted bytes. network.runNetwork() diff --git a/node/src/test/kotlin/com/r3corda/node/services/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/com/r3corda/node/services/NodeAttachmentStorageTest.kt index e2a258d1a0..c759f35314 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/NodeAttachmentStorageTest.kt @@ -4,12 +4,19 @@ import com.codahale.metrics.MetricRegistry import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import com.r3corda.core.crypto.SecureHash -import com.r3corda.core.use +import com.r3corda.core.crypto.sha256 +import com.r3corda.core.div +import com.r3corda.core.read +import com.r3corda.core.readAll +import com.r3corda.core.write import com.r3corda.node.services.persistence.NodeAttachmentService import org.junit.Before import org.junit.Test import java.nio.charset.Charset -import java.nio.file.* +import java.nio.file.FileAlreadyExistsException +import java.nio.file.FileSystem +import java.nio.file.Path +import java.nio.file.StandardOpenOption.WRITE import java.util.jar.JarEntry import java.util.jar.JarOutputStream import kotlin.test.assertEquals @@ -28,10 +35,10 @@ class NodeAttachmentStorageTest { @Test fun `insert and retrieve`() { val testJar = makeTestJar() - val expectedHash = SecureHash.sha256(Files.readAllBytes(testJar)) + val expectedHash = testJar.readAll().sha256() val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry()) - val id = testJar.use { storage.importAttachment(it) } + val id = testJar.read { storage.importAttachment(it) } assertEquals(expectedHash, id) assertNull(storage.openAttachment(SecureHash.randomSHA256())) @@ -48,9 +55,9 @@ class NodeAttachmentStorageTest { fun `duplicates not allowed`() { val testJar = makeTestJar() val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry()) - testJar.use { storage.importAttachment(it) } + testJar.read { storage.importAttachment(it) } assertFailsWith { - testJar.use { storage.importAttachment(it) } + testJar.read { storage.importAttachment(it) } } } @@ -58,15 +65,15 @@ class NodeAttachmentStorageTest { fun `corrupt entry throws exception`() { val testJar = makeTestJar() val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry()) - val id = testJar.use { storage.importAttachment(it) } + val id = testJar.read { storage.importAttachment(it) } // Corrupt the file in the store. - Files.write(fs.getPath("/", id.toString()), "arggghhhh".toByteArray(), StandardOpenOption.WRITE) + fs.getPath("/", id.toString()).write(options = WRITE) { it.write("arggghhhh".toByteArray()) } val e = assertFailsWith { storage.openAttachment(id)!!.open().use { it.readBytes() } } - assertEquals(e.file, storage.storePath.resolve(id.toString())) + assertEquals(e.file, storage.storePath / id.toString()) // But if we skip around and read a single entry, no exception is thrown. storage.openAttachment(id)!!.openAsJAR().use { @@ -78,15 +85,16 @@ class NodeAttachmentStorageTest { private var counter = 0 private fun makeTestJar(): Path { counter++ - val f = fs.getPath("$counter.jar") - JarOutputStream(Files.newOutputStream(f)).use { - it.putNextEntry(JarEntry("test1.txt")) - it.write("This is some useful content".toByteArray()) - it.closeEntry() - it.putNextEntry(JarEntry("test2.txt")) - it.write("Some more useful content".toByteArray()) - it.closeEntry() + val file = fs.getPath("$counter.jar") + file.write { + val jar = JarOutputStream(it) + jar.putNextEntry(JarEntry("test1.txt")) + jar.write("This is some useful content".toByteArray()) + jar.closeEntry() + jar.putNextEntry(JarEntry("test2.txt")) + jar.write("Some more useful content".toByteArray()) + jar.closeEntry() } - return f + return file } } \ No newline at end of file diff --git a/node/src/test/kotlin/com/r3corda/node/services/PropertiesFileRPCUserServiceTest.kt b/node/src/test/kotlin/com/r3corda/node/services/PropertiesFileRPCUserServiceTest.kt index dc55e60182..853ba8fdfb 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/PropertiesFileRPCUserServiceTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/PropertiesFileRPCUserServiceTest.kt @@ -2,11 +2,11 @@ package com.r3corda.node.services import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs +import com.r3corda.core.writeLines import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Test -import java.nio.file.Files class PropertiesFileRPCUserServiceTest { @@ -75,7 +75,7 @@ class PropertiesFileRPCUserServiceTest { } private fun loadWithContents(vararg lines: String): PropertiesFileRPCUserService { - Files.write(file, lines.asList()) + file.writeLines(lines.asList()) return PropertiesFileRPCUserService(file) } } \ No newline at end of file diff --git a/node/src/test/kotlin/com/r3corda/node/utilities/certsigning/CertificateSignerTest.kt b/node/src/test/kotlin/com/r3corda/node/utilities/certsigning/CertificateSignerTest.kt index 1101eeeff1..7819147c66 100644 --- a/node/src/test/kotlin/com/r3corda/node/utilities/certsigning/CertificateSignerTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/utilities/certsigning/CertificateSignerTest.kt @@ -6,11 +6,12 @@ import com.nhaarman.mockito_kotlin.mock import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.X509Utilities import com.r3corda.core.div +import com.r3corda.core.exists +import com.r3corda.core.readLines import com.r3corda.node.services.config.NodeConfiguration import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.nio.file.Files import java.nio.file.Path import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -47,13 +48,13 @@ class CertificateSignerTest { override val trustStorePassword: String = "trustpass" } - assertFalse(Files.exists(config.keyStorePath)) - assertFalse(Files.exists(config.trustStorePath)) + assertFalse(config.keyStorePath.exists()) + assertFalse(config.trustStorePath.exists()) CertificateSigner(config, certService).buildKeyStore() - assertTrue(Files.exists(config.keyStorePath)) - assertTrue(Files.exists(config.trustStorePath)) + assertTrue(config.keyStorePath.exists()) + assertTrue(config.trustStorePath.exists()) X509Utilities.loadKeyStore(config.keyStorePath, config.keyStorePassword).run { assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY)) @@ -73,7 +74,7 @@ class CertificateSignerTest { assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY)) } - assertEquals(id, Files.readAllLines(config.certificatesPath / "certificate-request-id.txt").first()) + assertEquals(id, (config.certificatesPath / "certificate-request-id.txt").readLines { it.findFirst().get() }) } } \ No newline at end of file diff --git a/test-utils/src/main/kotlin/com/r3corda/testing/node/MockNode.kt b/test-utils/src/main/kotlin/com/r3corda/testing/node/MockNode.kt index e7cecabf43..9ef1fd9cfd 100644 --- a/test-utils/src/main/kotlin/com/r3corda/testing/node/MockNode.kt +++ b/test-utils/src/main/kotlin/com/r3corda/testing/node/MockNode.kt @@ -3,7 +3,10 @@ package com.r3corda.testing.node import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs import com.google.common.util.concurrent.Futures +import com.r3corda.core.createDirectories +import com.r3corda.core.createDirectory import com.r3corda.core.crypto.Party +import com.r3corda.core.div import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.core.node.PhysicalLocation import com.r3corda.core.node.services.KeyManagementService @@ -13,11 +16,9 @@ import com.r3corda.core.random63BitValue import com.r3corda.core.utilities.DUMMY_NOTARY_KEY import com.r3corda.core.utilities.loggerFor import com.r3corda.node.internal.AbstractNode -import com.r3corda.node.internal.CordaRPCOpsImpl import com.r3corda.node.services.api.MessagingServiceInternal import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.keys.E2ETestKeyManagementService -import com.r3corda.node.services.messaging.CordaRPCOps import com.r3corda.node.services.messaging.RPCOps import com.r3corda.node.services.network.InMemoryNetworkMapService import com.r3corda.node.services.network.NetworkMapService @@ -29,7 +30,6 @@ import com.r3corda.node.utilities.AffinityExecutor import com.r3corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import org.slf4j.Logger import java.nio.file.FileSystem -import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair import java.util.* @@ -66,7 +66,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, val nodes: List = _nodes init { - Files.createDirectory(filesystem.getPath("/nodes")) + filesystem.getPath("/nodes").createDirectory() } /** Allows customisation of how nodes are created. */ @@ -182,7 +182,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, val path = filesystem.getPath("/nodes/$id") if (newNode) - Files.createDirectories(path.resolve("attachments")) + (path / "attachments").createDirectories() // TODO: create a base class that provides a default implementation val config = object : NodeConfiguration {