From 0f73b68d3951b0c1dcb508bcff761f79a02ea23c Mon Sep 17 00:00:00 2001 From: Chris Rankin <chris.rankin@r3.com> Date: Mon, 20 Feb 2017 15:23:27 +0000 Subject: [PATCH] CORPRIV-661: Implement saving profiles. --- .../net/corda/demobench/model/NodeConfig.kt | 2 +- .../corda/demobench/model/NodeController.kt | 5 +- .../demobench/profile/ProfileController.kt | 57 +++++++++----- .../kotlin/net/corda/demobench/rpc/NodeRPC.kt | 2 +- .../corda/demobench/views/DemoBenchView.kt | 76 ++++++++++++------- .../net/corda/demobench/views/NodeTabView.kt | 1 + .../corda/demobench/views/DemoBenchView.fxml | 3 +- 7 files changed, 94 insertions(+), 52 deletions(-) diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt index 7c6aac3464..0034101c98 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt @@ -66,7 +66,7 @@ class NodeConfig( .withValue("h2port", valueFor(h2Port)) .withValue("useTestClock", valueFor(true)) - fun toText() = toFileConfig().root().render(renderOptions) + fun toText(): String = toFileConfig().root().render(renderOptions) fun moveTo(baseDir: Path) = NodeConfig( baseDir, legalName, artemisPort, nearestCity, webPort, h2Port, extraServices, users diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index a946f795b6..d8589a3a89 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -5,7 +5,6 @@ import java.lang.management.ManagementFactory import java.net.ServerSocket import java.text.SimpleDateFormat import java.util.* -import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger import net.corda.demobench.pty.R3Pty import tornadofx.Controller @@ -27,13 +26,13 @@ class NodeController : Controller() { private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar") private val command = jvm.commandFor(cordaPath) - private val nodes = ConcurrentHashMap<String, NodeConfig>() + private val nodes = LinkedHashMap<String, NodeConfig>() private val port = AtomicInteger(FIRST_PORT) private var networkMapConfig: NetworkMapConfig? = null val activeNodes: List<NodeConfig> get() = nodes.values.filter { - it.state == NodeState.RUNNING + (it.state == NodeState.RUNNING) || (it.state == NodeState.STARTING) } init { diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/profile/ProfileController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/profile/ProfileController.kt index 2bd2024d96..8e5438f19f 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/profile/ProfileController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/profile/ProfileController.kt @@ -3,10 +3,13 @@ package net.corda.demobench.profile import com.google.common.net.HostAndPort import com.typesafe.config.Config import com.typesafe.config.ConfigFactory -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path +import java.io.File +import java.net.URI +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes import java.util.* +import java.util.function.BiPredicate import javafx.stage.FileChooser import javafx.stage.FileChooser.ExtensionFilter import net.corda.demobench.model.* @@ -14,6 +17,10 @@ import tornadofx.Controller class ProfileController : Controller() { + private companion object ConfigAcceptor : BiPredicate<Path, BasicFileAttributes> { + override fun test(p: Path?, attr: BasicFileAttributes?) = "node.conf" == p?.fileName.toString() + } + private val jvm by inject<JVMConfig>() private val baseDir = jvm.userHome.resolve("demobench") private val nodeController by inject<NodeController>() @@ -21,34 +28,50 @@ class ProfileController : Controller() { private val chooser = FileChooser() init { + chooser.title = "DemoBench Profiles" chooser.initialDirectory = baseDir.toFile() chooser.extensionFilters.add(ExtensionFilter("DemoBench profiles (*.zip)", "*.zip", "*.ZIP")) } - fun saveAs() { - log.info("Save as") - } + fun saveProfile(): Boolean { + var target = chooser.showSaveDialog(null) ?: return false + if (target.extension.isEmpty()) { + target = File(target.parent, target.name + ".zip") + } - fun save() { - log.info("Save") + log.info("Save profile as: $target") + + val configs = nodeController.activeNodes + + FileSystems.newFileSystem(URI.create("jar:" + target.toURI()), mapOf("create" to "true")).use { + fs -> configs.forEach { it -> + val nodeDir = Files.createDirectories(fs.getPath(it.key)) + val conf = Files.write(nodeDir.resolve("node.conf"), it.toText().toByteArray(UTF_8)) + log.info("Wrote: $conf") + } + } + + return true } fun openProfile(): List<NodeConfig>? { val chosen = chooser.showOpenDialog(null) ?: return null - log.info("Selected profile: ${chosen}") + log.info("Selected profile: $chosen") val configs = LinkedList<NodeConfig>() FileSystems.newFileSystem(chosen.toPath(), null).use { fs -> fs.rootDirectories.forEach { - root -> Files.walk(root).forEach { - if ((it.nameCount == 2) && ("node.conf" == it.fileName.toString())) { - try { - configs.add(toNodeConfig(parse(it))) - } catch (e: Exception) { - log.severe("Failed to parse '$it': ${e.message}") - throw e - } + root -> Files.find(root, 2, ConfigAcceptor).forEach { + try { + // Java seems to "walk" through the ZIP file backwards. + // So add new config to the front of the list, so that + // our final list is ordered to match the file. + configs.addFirst(toNodeConfig(parse(it))) + log.info("Loaded: $it") + } catch (e: Exception) { + log.severe("Failed to parse '$it': ${e.message}") + throw e } } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt index abfec68131..d88989739d 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt @@ -10,7 +10,7 @@ import net.corda.node.services.messaging.CordaRPCClient class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit): AutoCloseable { - private companion object Data { + private companion object { val log = loggerFor<NodeRPC>() val ONE_SECOND = SECONDS.toMillis(1) } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt index dd262976d4..cef277c1b8 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt @@ -23,39 +23,51 @@ class DemoBenchView : View("Corda Demo Bench") { private val addNodeButton by fxid<Button>() private val nodeTabPane by fxid<TabPane>() private val menuOpen by fxid<MenuItem>() - private val menuSave by fxid<MenuItem>() private val menuSaveAs by fxid<MenuItem>() init { importStylesheet("/net/corda/demobench/style.css") - primaryStage.setOnCloseRequest { - log.info("Exiting") + configureShutdown() - // Prevent any new NodeTabViews from being created. - addNodeButton.isDisable = true + configureProfileSaveAs() + configureProfileOpen() - closeAllTabs() - Platform.exit() - } + configureAddNode() + } - menuSaveAs.setOnAction { - profileController.saveAs() - } - menuSave.setOnAction { - profileController.save() - } - menuOpen.setOnAction { - try { - val profile = profileController.openProfile() - if (profile != null) { - loadProfile(profile) - } - } catch (e: Exception) { - ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait() + private fun configureShutdown() = primaryStage.setOnCloseRequest { + log.info("Exiting") + + // Prevent any new NodeTabViews from being created. + addNodeButton.isDisable = true + + closeAllTabs() + Platform.exit() + } + + private fun configureProfileSaveAs() = menuSaveAs.setOnAction { + try { + if (profileController.saveProfile()) { + menuSaveAs.isDisable = true } + } catch (e: Exception) { + ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait() } + } + private fun configureProfileOpen() = menuOpen.setOnAction { + try { + val profile = profileController.openProfile() + if (profile != null) { + loadProfile(profile) + } + } catch (e: Exception) { + ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait() + } + } + + private fun configureAddNode() { addNodeButton.setOnAction { val nodeTabView = createNodeTabView(true) nodeTabPane.selectionModel.select(nodeTabView.nodeTab) @@ -66,19 +78,17 @@ class DemoBenchView : View("Corda Demo Bench") { addNodeButton.fire() } - private fun closeAllTabs() { - ArrayList<Tab>(nodeTabPane.tabs).forEach { - (it as CloseableTab).requestClose() - } + private fun closeAllTabs() = ArrayList<Tab>(nodeTabPane.tabs).forEach { + (it as CloseableTab).requestClose() } - fun createNodeTabView(showConfig: Boolean): NodeTabView { + private fun createNodeTabView(showConfig: Boolean): NodeTabView { val nodeTabView = find<NodeTabView>(mapOf("showConfig" to showConfig)) nodeTabPane.tabs.add(nodeTabView.nodeTab) return nodeTabView } - fun loadProfile(nodes: List<NodeConfig>) { + private fun loadProfile(nodes: List<NodeConfig>) { closeAllTabs() nodeController.reset() @@ -90,6 +100,16 @@ class DemoBenchView : View("Corda Demo Bench") { enableAddNodes() } + /** + * Enable the "save profile" menu item. + */ + fun enableSaveProfile() { + menuSaveAs.isDisable = false + } + + /** + * Enables the button that allows us to create a new node. + */ fun enableAddNodes() { addNodeButton.isDisable = false } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index c8671e75a4..73e090e441 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -155,6 +155,7 @@ class NodeTabView : Fragment() { if (model.validate()) { launch() main.enableAddNodes() + main.enableSaveProfile() } } } diff --git a/tools/demobench/src/main/resources/net/corda/demobench/views/DemoBenchView.fxml b/tools/demobench/src/main/resources/net/corda/demobench/views/DemoBenchView.fxml index 7c52ca7c3c..31a44f4f3f 100644 --- a/tools/demobench/src/main/resources/net/corda/demobench/views/DemoBenchView.fxml +++ b/tools/demobench/src/main/resources/net/corda/demobench/views/DemoBenchView.fxml @@ -13,8 +13,7 @@ <MenuBar> <Menu text="File"> <MenuItem fx:id="menuOpen" text="Open"/> - <MenuItem fx:id="menuSave" text="Save"/> - <MenuItem fx:id="menuSaveAs" text="Save As"/> + <MenuItem fx:id="menuSaveAs" disable="true" text="Save As"/> </Menu> </MenuBar> <StackPane>