mirror of
https://github.com/corda/corda.git
synced 2025-02-01 00:45:59 +00:00
Shell: add support for uploading and downloading attachments.
This commit is contained in:
parent
347224c900
commit
ac90fe724e
@ -18,7 +18,11 @@ import net.corda.testing.node.MockNetwork
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.security.SignatureException
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarOutputStream
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotNull
|
||||
@ -134,9 +138,18 @@ class ResolveTransactionsFlowTest {
|
||||
|
||||
@Test
|
||||
fun attachment() {
|
||||
fun makeJar(): InputStream {
|
||||
val bs = ByteArrayOutputStream()
|
||||
val jar = JarOutputStream(bs)
|
||||
jar.putNextEntry(JarEntry("TEST"))
|
||||
jar.write("Some test file".toByteArray())
|
||||
jar.closeEntry()
|
||||
jar.close()
|
||||
return bs.toByteArray().opaque().open()
|
||||
}
|
||||
// TODO: this operation should not require an explicit transaction
|
||||
val id = databaseTransaction(a.database) {
|
||||
a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
|
||||
a.services.storageService.attachments.importAttachment(makeJar())
|
||||
}
|
||||
val stx2 = makeTransactions(withAttachment = id).second
|
||||
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
|
||||
|
419
docs/build/html/shell.html
vendored
Normal file
419
docs/build/html/shell.html
vendored
Normal file
@ -0,0 +1,419 @@
|
||||
<!-- If you edit this, then please make the same changes to layout_for_doc_website.html, as that is used for the web
|
||||
doc site generation which we put analytics tracking on to identify any potential problem pages -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>Shell — R3 Corda latest documentation</title>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="top" title="R3 Corda latest documentation" href="index.html"/>
|
||||
<link rel="next" title="Object Serialization" href="serialization.html"/>
|
||||
<link rel="prev" title="The example CorDapp" href="tutorial-cordapp.html"/>
|
||||
|
||||
|
||||
<script src="_static/js/modernizr.min.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
|
||||
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="index.html" class="icon icon-home"> R3 Corda
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="version">
|
||||
latest
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div role="search">
|
||||
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<br>
|
||||
API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/javadoc/index.html">JavaDoc</a>
|
||||
<br>
|
||||
<a href="https://discourse.corda.net">Discourse Forums</a>
|
||||
<br>
|
||||
<a href="http://slack.corda.net">Slack</a>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
|
||||
|
||||
|
||||
|
||||
<p class="caption"><span class="caption-text">Getting started</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="inthebox.html">What’s included?</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="getting-set-up.html">Getting set up</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="getting-set-up-fault-finding.html">Troubleshooting</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="running-the-demos.html">Running the demos</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="CLI-vs-IDE.html">CLI vs IDE</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Key concepts</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts.html">Overview</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-ecosystem.html">Corda ecosystem</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-data-model.html">Data model</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-core-types.html">Core types</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-financial-model.html">Financial model</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-flow-framework.html">Flow framework</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-consensus-notaries.html">Consensus and notaries</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-vault.html">Vault</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="key-concepts-security-model.html">Security model</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">CorDapps</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="creating-a-cordapp.html">CorDapp basics</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-cordapp.html">The example CorDapp</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">The Corda node</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Shell</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#getting-help">Getting help</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#starting-flows-and-performing-remote-method-calls">Starting flows and performing remote method calls</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#data-syntax">Data syntax</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#attachments">Attachments</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#extending-the-shell">Extending the shell</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#limitations">Limitations</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="serialization.html">Object Serialization</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="clientrpc.html">Client RPC</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="messaging.html">Networking and messaging</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="persistence.html">Persistence</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="node-administration.html">Node administration</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="corda-configuration-file.html">Node configuration</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="corda-plugins.html">The Corda plugin framework</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="node-services.html">Brief introduction to the node services</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="node-explorer.html">Node Explorer</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="permissioning.html">Network permissioning</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Tutorials</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-contract.html">Writing a contract</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-contract-clauses.html">Writing a contract using clauses</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-test-dsl.html">Writing a contract test</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="contract-upgrade.html">Upgrading Contracts</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-integration-testing.html">Integration testing</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-clientrpc-api.html">Client RPC API tutorial</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-building-transactions.html">Building transactions</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flow-state-machines.html">Writing flows</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flow-testing.html">Writing flow tests</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="running-a-notary.html">Running a notary service</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="using-a-notary.html">Using a notary service</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="oracles.html">Writing oracle services</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="tutorial-attachments.html">Using attachments</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="event-scheduling.html">Event scheduling</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Other</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="network-simulator.html">Network Simulator</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="clauses.html">Clauses</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="merkle-trees.html">Transaction tear-offs</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="json.html">JSON</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Component library</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="contract-catalogue.html">Contract catalogue</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="contract-irs.html">Interest rate swaps</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Appendix</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="loadtesting.html">Load testing</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="setting-up-a-corda-network.html">What is a corda network?</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="secure-coding-guidelines.html">Secure coding guidelines</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="release-process.html">Release process</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="release-notes.html">Release notes</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="changelog.html">Changelog</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="codestyle.html">Code style guide</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="building-the-docs.html">Building the documentation</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="further-notes-on-kotlin.html">Further notes on Kotlin</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="publishing-corda.html">Publishing Corda</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="azure-vm.html">Working with the Corda Demo on Azure Marketplace</a></li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Glossary</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="glossary.html">Glossary</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
|
||||
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="index.html">R3 Corda</a>
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="index.html">Docs</a> »</li>
|
||||
|
||||
<li>Shell</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
|
||||
|
||||
<a href="_sources/shell.txt" rel="nofollow"> View page source</a>
|
||||
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div itemprop="articleBody">
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script><div class="section" id="shell">
|
||||
<h1>Shell<a class="headerlink" href="#shell" title="Permalink to this headline">¶</a></h1>
|
||||
<p>The Corda shell is an embedded command line that allows an administrator to control and monitor the node.
|
||||
Some of its features include:</p>
|
||||
<ul class="simple">
|
||||
<li>Invoking any of the RPCs the node exposes to applications.</li>
|
||||
<li>Starting flows.</li>
|
||||
<li>View a dashboard of threads, heap usage, VM properties.</li>
|
||||
<li>Uploading and downloading zips from the attachment store.</li>
|
||||
<li>Issue SQL queries to the underlying database.</li>
|
||||
<li>View JMX metrics and monitoring exports.</li>
|
||||
<li>UNIX style pipes for both text and objects, an <code class="docutils literal"><span class="pre">egrep</span></code> command and a command for working with columnular data.</li>
|
||||
</ul>
|
||||
<div class="admonition note">
|
||||
<p class="first admonition-title">Note</p>
|
||||
<p class="last">A future version of Corda will add SSH access to the node.</p>
|
||||
</div>
|
||||
<p>It is based on the popular <a class="reference external" href="http://www.crashub.org/">CRaSH</a> shell used in various other projects and supports many of the same features.</p>
|
||||
<p>The shell may be disabled by passing the <code class="docutils literal"><span class="pre">--no-local-shell</span></code> flag to the node.</p>
|
||||
<div class="section" id="getting-help">
|
||||
<h2>Getting help<a class="headerlink" href="#getting-help" title="Permalink to this headline">¶</a></h2>
|
||||
<p>You can run <code class="docutils literal"><span class="pre">help</span></code> to list the available commands.</p>
|
||||
<p>The shell has a <code class="docutils literal"><span class="pre">man</span></code> command that can be used to get interactive help on many commands. You can also use the
|
||||
<code class="docutils literal"><span class="pre">--help</span></code> or <code class="docutils literal"><span class="pre">-h</span></code> flags to a command to get info about what switches it supports.</p>
|
||||
<p>Commands may have subcommands, in the same style as <code class="docutils literal"><span class="pre">git</span></code>. In that case running the command by itself will
|
||||
list the supported subcommands.</p>
|
||||
</div>
|
||||
<div class="section" id="starting-flows-and-performing-remote-method-calls">
|
||||
<h2>Starting flows and performing remote method calls<a class="headerlink" href="#starting-flows-and-performing-remote-method-calls" title="Permalink to this headline">¶</a></h2>
|
||||
<p><strong>Flows</strong> are the way the ledger is changed. If you aren’t familiar with them, please review “<a class="reference internal" href="flow-state-machines.html"><span class="doc">Writing flows</span></a>”
|
||||
first. The <code class="docutils literal"><span class="pre">flow</span> <span class="pre">list</span></code> command can be used to list the flows understood by the node and <code class="docutils literal"><span class="pre">flow</span> <span class="pre">start</span></code> can be
|
||||
used to start them. The <code class="docutils literal"><span class="pre">flow</span> <span class="pre">start</span></code> command takes the class name of a flow, or <em>any unambiguous substring</em> and
|
||||
then the data to be passed to the flow constructor. The unambiguous substring feature is helpful for reducing
|
||||
the needed typing. If the match is ambiguous the possible matches will be printed out. If a flow has multiple
|
||||
constructors then the names and types of the arguments will be used to try and determine which to use automatically.
|
||||
If the match against available constructors is unclear, the reasons each available constructor failed to match
|
||||
will be printed out. In the case of an ambiguous match, the first applicable will be used.</p>
|
||||
<p><strong>RPCs</strong> (remote procedure calls) are commands that can be sent to the node to query it, control it and manage it.
|
||||
RPCs don’t typically do anything that changes the global ledger, but they may change node-specific data in the
|
||||
database. Each RPC is one method on the <code class="docutils literal"><span class="pre">CordaRPCOps</span></code> interface, and may return a stream of events that will
|
||||
be shown on screen until you press Ctrl-C. You perform an RPC by using <code class="docutils literal"><span class="pre">run</span></code> followed by the name.</p>
|
||||
<center><b><a href="api/kotlin/corda/net.corda.core.messaging/-corda-r-p-c-ops/index.html">Documentation of available RPCs</a></b><p></center><p>Whichever form of change is used, there is a need to provide <em>parameters</em> to either the RPC or the flow
|
||||
constructor. Because parameters can be any arbitrary Java object graph, we need a convenient syntax to express
|
||||
this sort of data. The shell uses a syntax called <a class="reference external" href="http://www.yaml.org/spec/1.2/spec.html">Yaml</a> to do this.</p>
|
||||
</div>
|
||||
<div class="section" id="data-syntax">
|
||||
<h2>Data syntax<a class="headerlink" href="#data-syntax" title="Permalink to this headline">¶</a></h2>
|
||||
<p>Yaml (yet another markup language) is a simple JSON-like way to describe object graphs. It has several features
|
||||
that make it helpful for our use case, like a lightweight syntax and support for “bare words” which mean you can
|
||||
often skip the quotes around strings. Here is an example of how this syntax is used:</p>
|
||||
<p><code class="docutils literal"><span class="pre">flow</span> <span class="pre">start</span> <span class="pre">CashIssue</span> <span class="pre">amount:</span> <span class="pre">$1000,</span> <span class="pre">issueRef:</span> <span class="pre">1234,</span> <span class="pre">recipient:</span> <span class="pre">Bank</span> <span class="pre">A,</span> <span class="pre">notary:</span> <span class="pre">Notary</span> <span class="pre">Service</span></code></p>
|
||||
<p>This invokes a constructor of a flow with the following prototype in the code:</p>
|
||||
<div class="codeset container">
|
||||
<div class="highlight-kotlin"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">CashIssueFlow</span><span class="p">(</span><span class="k">val</span> <span class="py">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p"><</span><span class="n">Currency</span><span class="p">>,</span>
|
||||
<span class="k">val</span> <span class="py">issueRef</span><span class="p">:</span> <span class="n">OpaqueBytes</span><span class="p">,</span>
|
||||
<span class="k">val</span> <span class="py">recipient</span><span class="p">:</span> <span class="n">Party</span><span class="p">,</span>
|
||||
<span class="k">val</span> <span class="py">notary</span><span class="p">:</span> <span class="n">Party</span><span class="p">)</span> <span class="p">:</span> <span class="n">AbstractCashFlow</span><span class="p">(</span><span class="n">progressTracker</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Here, everything after <code class="docutils literal"><span class="pre">CashIssue</span></code> is specifying the arguments to the constructor of a flow. In Yaml, an object
|
||||
is specified as a set of <code class="docutils literal"><span class="pre">key:</span> <span class="pre">value</span></code> pairs and in our form, we separate them by commas. There are a few things
|
||||
to note about this syntax:</p>
|
||||
<ul class="simple">
|
||||
<li>When a parameter is of type <code class="docutils literal"><span class="pre">Amount<Currency></span></code> you can write it as either one of the dollar symbol ($),
|
||||
pound (£), euro (€) followed by the amount as a decimal, or as the value followed by the ISO currency code
|
||||
e.g. “100.12 CHF”</li>
|
||||
<li><code class="docutils literal"><span class="pre">OpaqueBytes</span></code> is filled with the contents of whatever is provided as a string.</li>
|
||||
<li><code class="docutils literal"><span class="pre">Party</span></code> objects are looked up by name.</li>
|
||||
<li>Strings do not need to be surrounded by quotes unless they contain a comma or embedded quotes. This makes it
|
||||
a lot more convenient to type such strings.</li>
|
||||
</ul>
|
||||
<p>Other types also have sensible mappings from strings. See <a class="reference external" href="api/kotlin/corda/net.corda.jackson/-jackson-support/index.html">the defined parsers</a> for more information.</p>
|
||||
<p>Nested objects can be created using curly braces, as in <code class="docutils literal"><span class="pre">{</span> <span class="pre">a:</span> <span class="pre">1,</span> <span class="pre">b:</span> <span class="pre">2}</span></code>. This is helpful when no particular
|
||||
parser is defined for the type you need, for instance, if an API requires a <code class="docutils literal"><span class="pre">Pair<String,</span> <span class="pre">Int></span></code>
|
||||
which could be represented as <code class="docutils literal"><span class="pre">{</span> <span class="pre">first:</span> <span class="pre">foo,</span> <span class="pre">second:</span> <span class="pre">123</span> <span class="pre">}</span></code>.</p>
|
||||
<p>The same syntax is also used to specify the parameters for RPCs, accessed via the <code class="docutils literal"><span class="pre">run</span></code> command, like this:</p>
|
||||
<p><code class="docutils literal"><span class="pre">run</span> <span class="pre">getCashBalances</span></code></p>
|
||||
</div>
|
||||
<div class="section" id="attachments">
|
||||
<h2>Attachments<a class="headerlink" href="#attachments" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The shell can be used to upload and download attachments from the node interactively. To learn more, see
|
||||
the tutorial “<a class="reference internal" href="tutorial-attachments.html"><span class="doc">Using attachments</span></a>”.</p>
|
||||
</div>
|
||||
<div class="section" id="extending-the-shell">
|
||||
<h2>Extending the shell<a class="headerlink" href="#extending-the-shell" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The shell can be extended using commands written in either Java or <a class="reference external" href="http://groovy-lang.org/">Groovy</a> (Groovy is a scripting language that
|
||||
is Java compatible). Such commands have full access to the node internal APIs and thus can be used to achieve
|
||||
almost anything.</p>
|
||||
<p>A full tutorial on how to write such commands is out of scope for this documentation, to learn more please
|
||||
refer to the <a class="reference external" href="http://www.crashub.org/">CRaSH</a> documentation. New commands can be placed in the <code class="docutils literal"><span class="pre">shell-commands</span></code> subdirectory in the
|
||||
node directory. Edits to existing commands will be used automatically, but at this time commands added after the
|
||||
node has started won’t be automatically detected. Commands should be named in all lower case with either a
|
||||
<code class="docutils literal"><span class="pre">.java</span></code> or <code class="docutils literal"><span class="pre">.groovy</span></code> extension.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="first admonition-title">Warning</p>
|
||||
<p class="last">Commands written in Groovy ignore Java security checks, so have unrestricted access to node and JVM
|
||||
internals regardless of any sandboxing that may be in place. Don’t allow untrusted users to edit files in the
|
||||
shell-commands directory!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="limitations">
|
||||
<h2>Limitations<a class="headerlink" href="#limitations" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The shell will be enhanced over time. The currently known limitations include:</p>
|
||||
<ul class="simple">
|
||||
<li>You cannot use it to upload/download attachments.</li>
|
||||
<li>SSH access is currently not available.</li>
|
||||
<li>There is no command completion for flows or RPCs.</li>
|
||||
<li>Command history is not preserved across restarts.</li>
|
||||
<li>The <code class="docutils literal"><span class="pre">jdbc</span></code> command requires you to explicitly log into the database first.</li>
|
||||
<li>Commands placed in the <code class="docutils literal"><span class="pre">shell-commands</span></code> directory are only noticed after the node is restarted.</li>
|
||||
<li>The <code class="docutils literal"><span class="pre">jul</span></code> command advertises access to logs, but it doesn’t work with the logging framework we’re using.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
|
||||
|
||||
<a href="serialization.html" class="btn btn-neutral float-right" title="Object Serialization" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
|
||||
|
||||
|
||||
<a href="tutorial-cordapp.html" class="btn btn-neutral" title="The example CorDapp" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<p>
|
||||
© Copyright 2016, R3 Limited.
|
||||
|
||||
</p>
|
||||
</div>
|
||||
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT:'./',
|
||||
VERSION:'latest',
|
||||
COLLAPSE_INDEX:false,
|
||||
FILE_SUFFIX:'.html',
|
||||
HAS_SOURCE: true
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/underscore.js"></script>
|
||||
<script type="text/javascript" src="_static/doctools.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="_static/js/theme.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.StickyNav.enable();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
@ -14,10 +14,6 @@ API changes:
|
||||
using strings like "1000.00 USD" when writing. You can use any format supported by ``Amount.parseCurrency``
|
||||
as input.
|
||||
|
||||
|
||||
Milestone 10
|
||||
------------
|
||||
|
||||
* Configuration:
|
||||
* Replace ``artemisPort`` with ``p2pPort`` in Gradle configuration
|
||||
* Replace ``artemisAddress`` with ``p2pAddress`` in node configuration
|
||||
|
@ -74,61 +74,4 @@ node is running out of memory, you can give it more by running the node like thi
|
||||
|
||||
The example command above would give a 1 gigabyte Java heap.
|
||||
|
||||
.. note:: Unfortunately the JVM does not let you limit the total memory usage of Java program, just the heap size.
|
||||
|
||||
Uploading and downloading attachments
|
||||
-------------------------------------
|
||||
|
||||
Attachments are files that add context to and influence the behaviour of transactions. They are always identified by
|
||||
hash and they are public, in that they propagate through the network to wherever they are needed.
|
||||
|
||||
All attachments are zip files. Thus to upload a file to the ledger you must first wrap it into a zip (or jar) file. Then
|
||||
you can upload it by running this command from a UNIX terminal:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
curl -F myfile=@path/to/my/file.zip http://localhost:31338/upload/attachment
|
||||
|
||||
The attachment will be identified by the SHA-256 hash of the contents, which you can get by doing:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
shasum -a 256 file.zip
|
||||
|
||||
on a Mac or by using ``sha256sum`` on Linux. Alternatively, the hash will be returned to you when you upload the
|
||||
attachment.
|
||||
|
||||
An attachment may be downloaded by fetching:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
http://localhost:31338/attachments/DECD098666B9657314870E192CED0C3519C2C9D395507A238338F8D003929DE9
|
||||
|
||||
where DECD... is of course replaced with the hash identifier of your own attachment. Because attachments are always
|
||||
containers, you can also fetch a specific file within the attachment by appending its path, like this:
|
||||
|
||||
.. sourcecode:: shell
|
||||
|
||||
http://localhost:31338/attachments/DECD098666B9657314870E192CED0C3519C2C9D395507A238338F8D003929DE9/path/within/zip.txt
|
||||
|
||||
Uploading interest rate fixes
|
||||
-----------------------------
|
||||
|
||||
If you would like to operate an interest rate fixing service (oracle), you can upload fix data by uploading data in
|
||||
a simple text format to the ``/upload/interest-rates`` path on the web server.
|
||||
|
||||
The file looks like this::
|
||||
|
||||
# Some pretend noddy rate fixes, for the interest rate oracles.
|
||||
|
||||
LIBOR 2016-03-16 1M = 0.678
|
||||
LIBOR 2016-03-16 2M = 0.655
|
||||
EURIBOR 2016-03-15 1M = 0.123
|
||||
EURIBOR 2016-03-15 2M = 0.111
|
||||
|
||||
The columns are:
|
||||
|
||||
* Name of the fix
|
||||
* Date of the fix
|
||||
* The tenor / time to maturity in days
|
||||
* The interest rate itself
|
||||
.. note:: Unfortunately the JVM does not let you limit the total memory usage of Java program, just the heap size.
|
@ -4,14 +4,14 @@ Release notes
|
||||
Here are release notes for each snapshot release from M9 onwards. This includes guidance on how to upgrade code from
|
||||
the previous milestone release.
|
||||
|
||||
Milestone 10
|
||||
------------
|
||||
UNRELEASED
|
||||
----------
|
||||
|
||||
Important: There are configuration changes in M10 due to the split of the Artemis port into separate P2P and RPC
|
||||
ports. To upgrade, you *must*:
|
||||
|
||||
1. In Gradle build configurations replace any references to ``artemisPort`` with ``p2pPort``.
|
||||
2. In node configurations replace ``artemisAddress`` with ``p2pAddress``.
|
||||
A new interactive **Corda Shell** has been added to the node. The shell lets developers and node administrators
|
||||
easily command the node by running flows, RPCs and SQL queries. It also provides a variety of commands to monitor
|
||||
the node. The Corda Shell is based on the popular `CRaSH project <http://www.crashub.org/>`_ and new commands can
|
||||
be easily added to the node by simply dropping Groovy or Java files into the node's ``shell-commands`` directory.
|
||||
We have many enhancements planned over time including SSH access, more commands and better tab completion.
|
||||
|
||||
Milestone 9
|
||||
-----------
|
||||
|
@ -13,6 +13,7 @@ Some of its features include:
|
||||
* Invoking any of the RPCs the node exposes to applications.
|
||||
* Starting flows.
|
||||
* View a dashboard of threads, heap usage, VM properties.
|
||||
* Uploading and downloading zips from the attachment store.
|
||||
* Issue SQL queries to the underlying database.
|
||||
* View JMX metrics and monitoring exports.
|
||||
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data.
|
||||
@ -101,6 +102,12 @@ The same syntax is also used to specify the parameters for RPCs, accessed via th
|
||||
|
||||
``run getCashBalances``
|
||||
|
||||
Attachments
|
||||
-----------
|
||||
|
||||
The shell can be used to upload and download attachments from the node interactively. To learn more, see
|
||||
the tutorial ":doc:`tutorial-attachments`".
|
||||
|
||||
Extending the shell
|
||||
-------------------
|
||||
|
||||
@ -123,7 +130,6 @@ Limitations
|
||||
|
||||
The shell will be enhanced over time. The currently known limitations include:
|
||||
|
||||
* You cannot use it to upload/download attachments.
|
||||
* SSH access is currently not available.
|
||||
* There is no command completion for flows or RPCs.
|
||||
* Command history is not preserved across restarts.
|
||||
|
@ -3,25 +3,51 @@
|
||||
Using attachments
|
||||
=================
|
||||
|
||||
Attachments are (typically large) Zip/Jar files referenced within a transaction, but not included in the transaction
|
||||
itself. These files can be requested from the originating node as needed, although in many cases will be cached within
|
||||
nodes already. Examples include:
|
||||
Attachments are ZIP/JAR files referenced from transaction by hash, but not included in the transaction
|
||||
itself. These files are automatically requested from the node sending the transaction when needed and cached
|
||||
locally so they are not re-requested if encountered again. Attachments typically contain:
|
||||
|
||||
* Contract executable code
|
||||
* Metadata about a transaction, such as PDF version of an invoice being settled
|
||||
* Shared information to be permanently recorded on the ledger
|
||||
|
||||
To add attachments the file must first be added to uploaded to the node, which returns a unique ID that can be added
|
||||
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and HTTP. For
|
||||
instructions on HTTP upload/download please see ":doc:`node-administration`".
|
||||
|
||||
Normally attachments on transactions are fetched automatically via the ``ResolveTransactionsFlow`` when verifying
|
||||
received transactions. Attachments are needed in order to validate a transaction (they include, for example, the
|
||||
contract code), so must be fetched before the validation process can run. ``ResolveTransactionsFlow`` calls
|
||||
``FetchTransactionsFlow`` to perform the actual retrieval.
|
||||
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and the Corda
|
||||
:doc:`shell`.
|
||||
|
||||
It is encouraged that where possible attachments are reusable data, so that nodes can meaningfully cache them.
|
||||
|
||||
Uploading and downloading
|
||||
-------------------------
|
||||
|
||||
To upload an attachment to the node, or download an attachment named by its hash, you use :doc:`clientrpc`. This
|
||||
is also available for interactive use via the shell. To **upload** run:
|
||||
|
||||
``>>> run uploadAttachment jar: /path/to/the/file.jar``
|
||||
|
||||
The file is uploaded, checked and if successful the hash of the file is returned. This is how the attachment is
|
||||
identified inside the node.
|
||||
|
||||
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
|
||||
can pass a simple ``InputStream`` or ``SecureHash`` to the ``uploadAttachment``/``openAttachment`` RPCs from
|
||||
a JVM client.
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
Normally attachments on transactions are fetched automatically via the ``ResolveTransactionsFlow``. Attachments
|
||||
are needed in order to validate a transaction (they include, for example, the contract code), so must be fetched
|
||||
before the validation process can run. ``ResolveTransactionsFlow`` calls ``FetchTransactionsFlow`` to perform the
|
||||
actual retrieval.
|
||||
|
||||
.. note:: Future versions of Corda may support non-critical attachments that are not used for transaction verification
|
||||
and which are shared explicitly. These are useful for attaching and signing auditing data with a transaction
|
||||
that isn't used as part of the contract logic.
|
||||
|
||||
Attachments demo
|
||||
----------------
|
||||
|
||||
|
@ -2,16 +2,21 @@ package net.corda.node
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.*
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
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.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
|
||||
@ -35,16 +40,16 @@ import org.crsh.vfs.spi.file.FileMountFactory
|
||||
import org.crsh.vfs.spi.url.ClassPathMountFactory
|
||||
import rx.Observable
|
||||
import rx.Subscriber
|
||||
import java.io.FileDescriptor
|
||||
import java.io.FileInputStream
|
||||
import java.io.PrintWriter
|
||||
import java.io.*
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
import kotlin.concurrent.thread
|
||||
@ -52,7 +57,6 @@ import kotlin.concurrent.thread
|
||||
// TODO: Add command history.
|
||||
// TODO: Command completion.
|
||||
// TODO: Find a way to inject this directly into CRaSH as a command, without needing JIT source compilation.
|
||||
// TODO: Add serialisers for InputStream so attachments can be uploaded through the shell.
|
||||
// TODO: Do something sensible with commands that return a future.
|
||||
// TODO: Configure default renderers, send objects down the pipeline, add commands to do json/xml/yaml outputs.
|
||||
// TODO: Add a command to view last N lines/tail/control log4j2 loggers.
|
||||
@ -76,7 +80,7 @@ object InteractiveShell {
|
||||
Logger.getLogger("").level = Level.OFF // TODO: Is this really needed?
|
||||
|
||||
val classpathDriver = ClassPathMountFactory(Thread.currentThread().contextClassLoader)
|
||||
val fileDriver = FileMountFactory(Utils.getCurrentDirectory());
|
||||
val fileDriver = FileMountFactory(Utils.getCurrentDirectory())
|
||||
|
||||
val extraCommandsPath = (dir / "shell-commands").toAbsolutePath()
|
||||
Files.createDirectories(extraCommandsPath)
|
||||
@ -158,12 +162,10 @@ object InteractiveShell {
|
||||
private val yamlInputMapper: ObjectMapper by lazy {
|
||||
// Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra
|
||||
// serializers.
|
||||
JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory())
|
||||
}
|
||||
|
||||
private object ObservableSerializer : JsonSerializer<Observable<*>>() {
|
||||
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString("(observable)")
|
||||
JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory()).apply {
|
||||
val rpcModule = SimpleModule()
|
||||
rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer)
|
||||
registerModule(rpcModule)
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,8 +173,9 @@ object InteractiveShell {
|
||||
return JacksonSupport.createNonRpcMapper(factory).apply({
|
||||
// Register serializers for stateful objects from libraries that are special to the RPC system and don't
|
||||
// make sense to print out to the screen. For classes we own, annotations can be used instead.
|
||||
val rpcModule = SimpleModule("RPC module")
|
||||
val rpcModule = SimpleModule()
|
||||
rpcModule.addSerializer(Observable::class.java, ObservableSerializer)
|
||||
rpcModule.addSerializer(InputStream::class.java, InputStreamSerializer)
|
||||
registerModule(rpcModule)
|
||||
|
||||
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
||||
@ -222,6 +225,8 @@ object InteractiveShell {
|
||||
} catch(e: NoApplicableConstructor) {
|
||||
output.println("No matching constructor found:", Color.red)
|
||||
e.errors.forEach { output.println("- $it", Color.red) }
|
||||
} finally {
|
||||
InputStreamDeserializer.closeAll()
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,7 +288,54 @@ object InteractiveShell {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CompletableFuture<Unit>? {
|
||||
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 <= ' ' })
|
||||
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
|
||||
// string form of the command.
|
||||
out.println("Please use the 'flow' command to interact with flows rather than the 'run' command.", Color.yellow)
|
||||
return null
|
||||
}
|
||||
|
||||
var result: Any? = null
|
||||
try {
|
||||
InputStreamSerializer.invokeContext = context
|
||||
val call = parser.parse(context.attributes["ops"] as CordaRPCOps, cmd)
|
||||
result = call.call()
|
||||
if (result != null && result !is kotlin.Unit && result !is Void) {
|
||||
result = printAndFollowRPCResponse(result, out)
|
||||
}
|
||||
if (result is Future<*>) {
|
||||
if (!result.isDone) {
|
||||
out.println("Waiting for completion or Ctrl-C ... ")
|
||||
out.flush()
|
||||
}
|
||||
try {
|
||||
result = result.get()
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
} catch (e: ExecutionException) {
|
||||
throw RuntimeException(e.rootCause)
|
||||
} catch (e: InvocationTargetException) {
|
||||
throw RuntimeException(e.rootCause)
|
||||
}
|
||||
}
|
||||
} catch (e: StringToMethodCallParser.UnparseableCallException) {
|
||||
out.println(e.message, Color.red)
|
||||
out.println("Please try 'man run' to learn what syntax is acceptable")
|
||||
} catch (e: Exception) {
|
||||
out.println("RPC failed: ${e.rootCause}", Color.red)
|
||||
} finally {
|
||||
InputStreamSerializer.invokeContext = null
|
||||
InputStreamDeserializer.closeAll()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): ListenableFuture<Unit>? {
|
||||
val printerFun = { obj: Any? -> yamlMapper.writeValueAsString(obj) }
|
||||
toStream.println(printerFun(response))
|
||||
toStream.flush()
|
||||
@ -291,13 +343,13 @@ object InteractiveShell {
|
||||
}
|
||||
|
||||
private class PrintingSubscriber(private val printerFun: (Any?) -> String, private val toStream: PrintWriter) : Subscriber<Any>() {
|
||||
private var count = 0;
|
||||
val future = CompletableFuture<Unit>()
|
||||
private var count = 0
|
||||
val future = SettableFuture.create<Unit>()!!
|
||||
|
||||
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.thenAccept {
|
||||
future then {
|
||||
if (!isUnsubscribed)
|
||||
unsubscribe()
|
||||
}
|
||||
@ -306,7 +358,7 @@ object InteractiveShell {
|
||||
@Synchronized
|
||||
override fun onCompleted() {
|
||||
toStream.println("Observable has completed")
|
||||
future.complete(Unit)
|
||||
future.set(Unit)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@ -320,13 +372,13 @@ object InteractiveShell {
|
||||
override fun onError(e: Throwable) {
|
||||
toStream.println("Observable completed with an error")
|
||||
e.printStackTrace()
|
||||
future.completeExceptionally(e)
|
||||
future.setException(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Kotlin bug: USELESS_CAST warning is generated below but the IDE won't let us remove it.
|
||||
@Suppress("USELESS_CAST", "UNCHECKED_CAST")
|
||||
private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): CompletableFuture<Unit>? {
|
||||
private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): SettableFuture<Unit>? {
|
||||
// Match on a couple of common patterns for "important" observables. It's tough to do this in a generic
|
||||
// way because observables can be embedded anywhere in the object graph, and can emit other arbitrary
|
||||
// object graphs that contain yet more observables. So we just look for top level responses that follow
|
||||
@ -347,4 +399,63 @@ object InteractiveShell {
|
||||
(observable as Observable<Any>).subscribe(subscriber)
|
||||
return subscriber.future
|
||||
}
|
||||
|
||||
//region Extra serializers
|
||||
//
|
||||
// These serializers are used to enable the user to specify objects that aren't natural data containers in the shell,
|
||||
// and for the shell to print things out that otherwise wouldn't be usefully printable.
|
||||
|
||||
private object ObservableSerializer : JsonSerializer<Observable<*>>() {
|
||||
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString("(observable)")
|
||||
}
|
||||
}
|
||||
|
||||
// A file name is deserialized to an InputStream if found.
|
||||
object InputStreamDeserializer : JsonDeserializer<InputStream>() {
|
||||
// Keep track of them so we can close them later.
|
||||
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)))) {
|
||||
override fun close() {
|
||||
super.close()
|
||||
streams.remove(this)
|
||||
}
|
||||
}
|
||||
streams += stream
|
||||
return stream
|
||||
}
|
||||
|
||||
fun closeAll() {
|
||||
// Clone the set with toList() here so each closed stream can be removed from the set inside close().
|
||||
streams.toList().forEach { Closeables.closeQuietly(it) }
|
||||
}
|
||||
}
|
||||
|
||||
// An InputStream found in a response triggers a request to the user to provide somewhere to save it.
|
||||
private object InputStreamSerializer : JsonSerializer<InputStream>() {
|
||||
var invokeContext: InvocationContext<*>? = null
|
||||
|
||||
override fun serialize(value: InputStream, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
try {
|
||||
val toPath = invokeContext!!.readLine("Path to save stream to (enter to ignore): ", true)
|
||||
if (toPath == null || toPath.isBlank()) {
|
||||
gen.writeString("<not saved>")
|
||||
} else {
|
||||
val path = Paths.get(toPath)
|
||||
path.write { value.copyTo(it) }
|
||||
gen.writeString("<saved to: $path>")
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
value.close()
|
||||
} catch(e: IOException) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
|
@ -117,9 +117,15 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
||||
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
|
||||
override fun importAttachment(jar: InputStream): SecureHash {
|
||||
require(jar !is JarInputStream)
|
||||
|
||||
// Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory.
|
||||
// TODO: Switch to a two-phase insert so we can handle attachments larger than RAM.
|
||||
// To do this we must pipe stream into the database without knowing its hash, which we will learn only once
|
||||
// the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not
|
||||
// set the hash field of the new attachment record.
|
||||
val hs = HashingInputStream(Hashing.sha256(), jar)
|
||||
val bytes = hs.readBytes()
|
||||
checkIsAValidJAR(hs)
|
||||
checkIsAValidJAR(ByteArrayInputStream(bytes))
|
||||
val id = SecureHash.SHA256(hs.hash().asBytes())
|
||||
|
||||
val count = session.withTransaction {
|
||||
@ -147,7 +153,7 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
||||
val extractTo = storePath / "$id.jar"
|
||||
try {
|
||||
extractTo.createDirectory()
|
||||
extractZipFile(hs, extractTo)
|
||||
extractZipFile(ByteArrayInputStream(bytes), extractTo)
|
||||
} catch(e: FileAlreadyExistsException) {
|
||||
log.trace("Did not extract attachment jar to directory because it already exists")
|
||||
} catch(e: Exception) {
|
||||
@ -164,7 +170,7 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
||||
// Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not
|
||||
// a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works.
|
||||
// So we have to check to ensure we found at least one item.
|
||||
val jar = JarInputStream(stream)
|
||||
val jar = JarInputStream(stream, true)
|
||||
var count = 0
|
||||
while (true) {
|
||||
val cursor = jar.nextJarEntry ?: break
|
||||
|
@ -6,6 +6,7 @@ import org.crsh.cli.*;
|
||||
import org.crsh.command.*;
|
||||
import org.crsh.text.*;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@ -37,46 +38,7 @@ public class run extends InteractiveShellCommand {
|
||||
return null;
|
||||
}
|
||||
|
||||
String cmd = String.join(" ", command).trim();
|
||||
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
|
||||
// string form of the command.
|
||||
out.println("Please use the 'flow' command to interact with flows rather than the 'run' command.", Color.yellow);
|
||||
return null;
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
StringToMethodCallParser.ParsedMethodCall call = parser.parse(ops(), cmd);
|
||||
result = call.call();
|
||||
result = processResult(result);
|
||||
} catch (StringToMethodCallParser.UnparseableCallException e) {
|
||||
out.println(e.getMessage(), Color.red);
|
||||
out.println("Please try 'man run' to learn what syntax is acceptable", Color.red);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object processResult(Object result) {
|
||||
if (result != null && !(result instanceof kotlin.Unit) && !(result instanceof Void)) {
|
||||
result = printAndFollowRPCResponse(result, out);
|
||||
}
|
||||
if (result instanceof Future) {
|
||||
Future future = (Future) result;
|
||||
if (!future.isDone()) {
|
||||
out.println("Waiting for completion or Ctrl-C ... ");
|
||||
out.flush();
|
||||
}
|
||||
try {
|
||||
result = future.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return InteractiveShell.runRPCFromString(command, out, context);
|
||||
}
|
||||
|
||||
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user