Address review comments

This commit is contained in:
Mike Hearn 2017-03-22 15:16:29 +01:00
parent ac90fe724e
commit 68ff33549a
9 changed files with 42 additions and 42 deletions

View File

@ -188,15 +188,14 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
/** Returns a string-to-string map of commands to a string describing available parameter types. */
val availableCommands: Map<String, String> get() {
return methodMap.map { entry ->
val (name, args) = entry
val argStr = if (args.parameterCount == 0) "" else {
return methodMap.mapValues { entry ->
val (name, args) = entry // TODO: Kotlin 1.1
if (args.parameterCount == 0) "" else {
val paramNames = methodParamNames[name]!!
val typeNames = args.parameters.map { it.type.simpleName }
val paramTypes = paramNames.zip(typeNames)
paramTypes.map { "${it.first}: ${it.second}" }.joinToString(", ")
}
Pair(name, argStr)
}.toMap()
}
}
}

View File

@ -32,7 +32,7 @@ To download an attachment, you can do:
``>>> run openAttachment id: AB7FED7663A3F195A59A0F01091932B15C22405CB727A1518418BF53C6E6663A``
which will then ask you to provide a path to save the file to. To do the same thing programmatically, you would
which will then ask you to provide a path to save the file to. To do the same thing programmatically, you
can pass a simple ``InputStream`` or ``SecureHash`` to the ``uploadAttachment``/``openAttachment`` RPCs from
a JVM client.

View File

@ -132,7 +132,7 @@ fun main(args: Array<String>) {
// Don't start the shell if there's no console attached.
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
node.startupComplete.thenAccept {
node.startupComplete then {
InteractiveShell.startShell(dir, runShell, cmdlineOptions.sshdServer, node)
}
} failure {

View File

@ -9,14 +9,11 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.google.common.io.Closeables
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.div
import net.corda.core.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStateMachine
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.rootCause
import net.corda.core.then
import net.corda.core.utilities.Emoji
import net.corda.core.write
import net.corda.jackson.JacksonSupport
import net.corda.jackson.StringToMethodCallParser
import net.corda.node.internal.Node
@ -82,8 +79,7 @@ object InteractiveShell {
val classpathDriver = ClassPathMountFactory(Thread.currentThread().contextClassLoader)
val fileDriver = FileMountFactory(Utils.getCurrentDirectory())
val extraCommandsPath = (dir / "shell-commands").toAbsolutePath()
Files.createDirectories(extraCommandsPath)
val extraCommandsPath = (dir / "shell-commands").toAbsolutePath().createDirectories()
val commandsFS = FS.Builder()
.register("file", fileDriver)
.mount("file:" + extraCommandsPath)
@ -195,7 +191,10 @@ object InteractiveShell {
@JvmStatic
fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter) {
val matches = node.flowLogicFactory.flowWhitelist.keys.filter { nameFragment in it }
if (matches.size > 1) {
if (matches.isEmpty()) {
output.println("No matching flow found, run 'flow list' to see your options.", Color.red)
return
} else if (matches.size > 1) {
output.println("Ambigous name provided, please be more specific. Your options are:")
matches.forEachIndexed { i, s -> output.println("${i+1}. $s", Color.yellow) }
return
@ -219,8 +218,6 @@ object InteractiveShell {
} catch(e: InterruptedException) {
ANSIProgressRenderer.progressTracker = null
// TODO: When the flow framework allows us to kill flows mid-flight, do so here.
} catch(e: ExecutionException) {
// It has already been logged by the framework code and printed by the ANSI progress renderer.
}
} catch(e: NoApplicableConstructor) {
output.println("No matching constructor found:", Color.red)
@ -246,7 +243,8 @@ object InteractiveShell {
*/
@Throws(NoApplicableConstructor::class)
fun runFlowFromString(invoke: (FlowLogic<*>) -> FlowStateMachine<*>,
inputData: String, clazz: Class<out FlowLogic<*>>,
inputData: String,
clazz: Class<out FlowLogic<*>>,
om: ObjectMapper = yamlInputMapper): FlowStateMachine<*> {
// For each constructor, attempt to parse the input data as a method call. Use the first that succeeds,
// and keep track of the reasons we failed so we can print them out if no constructors are usable.
@ -291,7 +289,7 @@ object InteractiveShell {
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>): Any? {
val parser = StringToMethodCallParser(CordaRPCOps::class.java, context.attributes["mapper"] as ObjectMapper)
val cmd = input.joinToString(" ").trim({ it <= ' ' })
val cmd = input.joinToString(" ").trim { it <= ' ' }
if (cmd.toLowerCase().startsWith("startflow")) {
// The flow command provides better support and startFlow requires special handling anyway due to
// the generic startFlow RPC interface which offers no type information with which to parse the
@ -318,9 +316,9 @@ object InteractiveShell {
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
} catch (e: ExecutionException) {
throw RuntimeException(e.rootCause)
throw e.rootCause
} catch (e: InvocationTargetException) {
throw RuntimeException(e.rootCause)
throw e.rootCause
}
}
} catch (e: StringToMethodCallParser.UnparseableCallException) {
@ -344,15 +342,12 @@ object InteractiveShell {
private class PrintingSubscriber(private val printerFun: (Any?) -> String, private val toStream: PrintWriter) : Subscriber<Any>() {
private var count = 0
val future = SettableFuture.create<Unit>()!!
val future: SettableFuture<Unit> = SettableFuture.create()
init {
// The future is public and can be completed by something else to indicate we don't wish to follow
// anymore (e.g. the user pressing Ctrl-C).
future then {
if (!isUnsubscribed)
unsubscribe()
}
future then { unsubscribe() }
}
@Synchronized
@ -417,7 +412,7 @@ object InteractiveShell {
private val streams = Collections.synchronizedSet(HashSet<InputStream>())
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): InputStream {
val stream = object : FilterInputStream(BufferedInputStream(Files.newInputStream(Paths.get(p.text)))) {
val stream = object : BufferedInputStream(Files.newInputStream(Paths.get(p.text))) {
override fun close() {
super.close()
streams.remove(this)

View File

@ -4,6 +4,7 @@ import com.codahale.metrics.JmxReporter
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.*
import net.corda.core.messaging.RPCOps
import net.corda.core.node.NodeVersionInfo
@ -12,7 +13,6 @@ import net.corda.core.node.Version
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.success
import net.corda.core.utilities.loggerFor
import net.corda.node.printBasicNodeInfo
import net.corda.node.serialization.NodeClock
@ -33,7 +33,6 @@ import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.nio.channels.FileLock
import java.time.Clock
import java.util.concurrent.CompletableFuture
import javax.management.ObjectName
import kotlin.concurrent.thread
@ -229,7 +228,7 @@ class Node(override val configuration: FullNodeConfiguration,
super.initialiseDatabasePersistence(insideTransaction)
}
val startupComplete = CompletableFuture<Unit>()
val startupComplete: ListenableFuture<Unit> = SettableFuture.create()
override fun start(): Node {
alreadyRunningNodeCheck()
@ -254,7 +253,7 @@ class Node(override val configuration: FullNodeConfiguration,
build().
start()
startupComplete.complete(Unit)
(startupComplete as SettableFuture<Unit>).set(Unit)
}
shutdownThread = thread(start = false) {

View File

@ -176,12 +176,12 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
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 || cursor.name == "." || cursor.name == "..")
throw IllegalArgumentException("Path is either absolute or non-normalised: $entryPath")
require(!entryPath.isAbsolute) { "Path $entryPath is absolute" }
require(entryPath.normalize() == entryPath) { "Path $entryPath is not normalised" }
require(!('\\' in cursor.name || cursor.name == "." || cursor.name == "..")) { "Bad character in $entryPath" }
count++
}
if (count == 0)
throw IllegalArgumentException("Stream is either empty or not a JAR/ZIP")
require(count > 0) { "Stream is either empty or not a JAR/ZIP" }
}
// Implementations for AcceptsFileUpload

View File

@ -18,6 +18,9 @@ import static net.corda.node.InteractiveShell.*;
)
@Usage("Start a (work)flow on the node. This is how you can change the ledger.")
public class flow extends InteractiveShellCommand {
// Note that the class name is deliberately lower case, because we want the command the user types to be
// lower case. CRaSH should ideally lowercase the command names for us, but it doesn't.
@Command
public void start(
@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name,

View File

@ -40,27 +40,27 @@ class InteractiveShellTest {
}
@Test
fun success() {
fun flowStartSimple() {
check("a: Hi there", "Hi there")
check("b: 12", "12")
check("b: 12, c: Yo", "12Yo")
}
@Test fun complex1() = check("amount: £10", "10.00 GBP")
@Test fun flowStartWithComplexTypes() = check("amount: £10", "10.00 GBP")
@Test fun complex2() = check(
@Test fun flowStartWithNestedTypes() = check(
"pair: { first: $100.12, second: df489807f81c8c8829e509e1bcb92e6692b9dd9d624b7456435cb2f51dc82587 }",
"($100.12, df489807f81c8c8829e509e1bcb92e6692b9dd9d624b7456435cb2f51dc82587)"
)
@Test(expected = InteractiveShell.NoApplicableConstructor::class)
fun noArgs() = check("", "")
fun flowStartNoArgs() = check("", "")
@Test(expected = InteractiveShell.NoApplicableConstructor::class)
fun missingParam() = check("c: Yo", "")
fun flowMissingParam() = check("c: Yo", "")
@Test(expected = InteractiveShell.NoApplicableConstructor::class)
fun tooManyArgs() = check("b: 12, c: Yo, d: Bar", "")
fun flowTooManyParams() = check("b: 12, c: Yo, d: Bar", "")
@Test
fun party() = check("party: SomeCorp", "SomeCorp")

View File

@ -126,12 +126,16 @@ class NodeAttachmentStorageTest {
}
}
@Test(expected = IllegalArgumentException::class)
@Test
fun `non jar rejected`() {
val storage = NodeAttachmentService(fs.getPath("/"), dataSourceProperties, MetricRegistry())
val path = fs.getPath("notajar")
path.writeLines(listOf("Hey", "there!"))
path.read { storage.importAttachment(it) }
path.read {
assertFailsWith<IllegalArgumentException>("either empty or not a JAR") {
storage.importAttachment(it)
}
}
}
private var counter = 0